Vuejs学习v1.0.1,持续更新中。。。

文章目录

      • 原创内容,转载请注明出处^_^
      • 基础知识
        • 为什么要学习Vuejs
        • 前后端分离暨Vuejs的优势
          • 变革
          • 开发
          • 运维
          • 对比
        • 什么是MVVM?MVVM是Model-View-ViewModel的缩写
        • 文件
      • 配置
        • 启动配置
      • 组件
        • 概念
          • vue-cli是vue.js的脚手架,用于自动生成vue.js+webpack的项目模板
          • export default
          • $refs
        • 定义组件
        • 注册组件
        • 生命周期
          • computed
        • 语法
          • Object.assign
      • 工具
        • 同步
          • Promise.all
          • Promise.race
        • 构建类
        • VueX
          • mutations
        • rules
        • Element-ui
          • 穿梭框踩坑
      • 踩坑
        • 错误记录
          • 简单
            • 代码不起效
            • Error in created hook: "TypeError: _admin.menuTree.then is not a function"
            • 请求头类型不对
            • 'vue-cli-service' 不是内部或外部命令,也不是可运行的程序
          • npm相关
          • Webpack相关
      • 简易功能开发
        • ajax
        • 登录
          • 逻辑
          • 源码解析
            • 后台API切换回Mock需改动
        • Token - 令牌
        • 全局变量
        • 引入模板
          • 弹窗
        • 编辑器
          • Json编辑器
        • 布局
          • 九宫格|十六宫格
      • 兼容
        • 解决IE兼容问题


版本号 作者 qq 备注
v1.0.1 飞豺 8416837 Vue 2.6.10

原创内容,转载请注明出处_

基础知识

为什么要学习Vuejs

  • 网友:"到今天这个时代有些人学完了js、html5/dom/bom直接跳过jQ去学vue我觉得完全没问题,所以不懂jQ的人会vue当然可以,本来前端的基础就是js而不是jQ。而且vue本身走的就是模块化的开发方式,就是让项目更好维护,更好升级,在这些方面绝对比jQ更优秀。jQuery之所以被替代,一是开发方式保守、老旧、效率低,二是因为html5出了很多新的api,完全可以代替jQ,就连bootstrap重构都已经申明将剔除jQ。退一万步说,前端最基础的还是js,只要你js技术过关,不管是学jQ还是vue都会很快。"
  • vue.js的作者尤雨溪是中国人,在知乎上有帐号,且非常活跃。
    尤雨溪毕业于上海复旦附中,在美国完成大学学业,本科毕业于Colgate University,后在Parsons设计学院获得Design & Technology艺术硕士学位,现就职于纽约Google Creative Lab。
    2016年9月3日,南京JSConf,尤雨溪宣布加盟阿里巴巴Weex团队,尤雨溪称他将以技术顾问的身份加入Weex 团队来做 Vue 和 Weex 的 JavaScript runtime 整合,目标是Vue跨三端。

前后端分离暨Vuejs的优势

变革
  • 解耦,视图-网关-服务各司其职。自由组合搭配。
  • 后端无状态,不存储用户信息,不维护session减轻负担。安全提升;
  • 单页模式,简化握手,响应更快;
  • 静态页面部署到nginx,响应更快,且升级时无感知,不需重启;
  • Reactive编程。响应式,数据改变时,页面实时响应。
  • 高效,将某些运算迁移到前端,类似边缘计算-分摊中心压力;
  • 简化代码,后端API机械式自动生成,前端调用API即可;
  • 技术健全,如路由、存储、生命周期管控、监听、计算、调试、测试框架,规范成熟;
  • 渐进式,自然融入到已经上线的项目,持续增加需求,扩展性强;
  • 数据渲染高效;
  • 优秀案例;
  • 技术革新、推广、传承;
开发
  • 上手较快,因为基础还是JS;
  • 热部署,代码更新后即时生效而无需构建。传统开发亦可以通过一定配置进行热部署,但有时会失效。
  • 灵活与API|MQ交互;
  • 兼容技术栈,如WebSocket、MQ等;
  • 丰富的组件库,提升效率;
  • 调试、测试框架;
  • 脱离后端,暂无API时,可使用mock.js框架模拟;
  • 语法糖有助于实现复杂功能;
  • 专用IDE,如webstorm;
  • 文档详尽易懂,社区活跃壮大中;
  • 开发体验:规范、生命周期控制,数据绑定,热部署不需复杂设置,JS与DOM解耦,不用直接操作DOM,代码复用,MVVM结构,面向对象-后端思维;
运维
  • 独立部署,减轻后端压力,发布以及服务崩溃互不影响;
  • 松散耦合,定位BUG快捷;
  • 代码易读,易维护,新增需求无压力,如多客户端需求、前后端分离需求;
  • 构建静态文件,防止开发技术外泄;
对比
  • Thymeleaf、JSP与DOM耦合,前者遵循xml规范,模板只解决了渲染,没解决麻烦的DOM问题;

什么是MVVM?MVVM是Model-View-ViewModel的缩写

要编写可维护的前端代码绝非易事。我们已经用MVC模式通过koa实现了后端数据、模板页面和控制器的分离,但是,对于前端来说,还不够。

这里有童鞋会问,不是讲Node后端开发吗?怎么又回到前端开发了?

对于一个全栈开发工程师来说,懂前端才会开发出更好的后端程序(不懂前端的后端工程师会设计出非常难用的API),懂后端才会开发出更好的前端程序。程序设计的基本思想在前后端都是通用的,两者并无本质的区别。这和“不想当厨子的裁缝不是好司机”是一个道理。
改变JavaScript对象的状态,会导致DOM结构作出对应的变化!这让我们的关注点从如何操作DOM变成了如何更新JavaScript对象的状态,而操作JavaScript对象比DOM简单多了!

这就是MVVM的设计思想:关注Model的变化,让MVVM框架去自动更新DOM的状态,从而把开发者从操作DOM的繁琐步骤中解脱出来! ——廖雪峰
ps:jQuery MVVM框架如JSViews

文件

  • Vue中index.html、main.js、App.vue、index.js之前的关系以及加载过程
    App.vue中的router-view
<template>
  <div id="app">
    <p>就是一张da图片p>
    [外链图片转存失败(img-dlDOl0R2-1562114250786)(https://mp.csdn.net/mdeditor/assets/logo.png)]
    
    <router-view/>
  div>
template>

router/index.js定义了简单的路由信息

  • main.js
    入口,加载组件到index.html

配置

启动配置

package.json文件配置:

  • 启动命令
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon -e js,graphql -x node -r dotenv/config ./src/index.js",
    "debug": "nodemon -e js,graphql -x node --inspect -r dotenv/config ./src/index.js",
    "lint": "eslint --ext .js src"
  },

比如,执行yarn start则走start脚本

组件

概念

vue-cli是vue.js的脚手架,用于自动生成vue.js+webpack的项目模板
  • 组件类似自定义元素.Web组件规范
  • 在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。假想例子,以便展示组件的结构↓
<div id="app">
  <app-nav>app-nav>
  <app-view>
    <app-sidebar>app-sidebar>
    <app-content>app-content>
  app-view>
div>
  • const
    常量
  • export
    文件通过export暴露接口|变量
  • h => h(App)
// 演变步骤
render: function (createElement) {
    return createElement(App);
}
render (createElement) {
    return createElement(App);
}
render (h){
    return h(App);
}

It comes from the term “hyperscript”, which is commonly used in many virtual-dom implementations. “Hyperscript” itself stands for “script that generates HTML structures” because HTML is the acronym for “hyper-text markup language”. – by 尤雨溪

export default
  • ES6的export
    使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件),没错
$refs

持有所有被ref定义的组件

定义组件

  • 在不使用.vue 单文件时,我们是通过 Vue 构造函数创建一个 Vue 根实例来启动vuejs 项目,Vue 构造函数接受一个对象,这个对象有一些配置属性 el, data, component, template 等,从而对整个应用提供支持。
  • new Vue()
    new Vue() 相当于一个构造函数,在入口文件 main.js 构造根组件的同时,如果根组件还包含其它子组件,那么 Vue 会通过引入的选项对象构造其对应的 Vue 实例,最终形成一棵组件树
  • export default
  • 比较new Vue() & export default
  • 全局组件
Vue.component('todo-item', {
        template: '
  • 这是个待办项
  • '
    })
    • .vue文件
      可以把html, css, js 写到一个文件中,从而实现了对一个组件的封装
    • 父子关系
      在一个组件中通过 import 引入另一个组件,这个组件就是父组件,被引入的组件就是子组件.
      父组件通过props 向子组件传递数据,子组件通过自定义事件向父组件传递数据.

    注册组件

    • 全局注册
    Vue.component('component-a', { /* ... */ })
    Vue.component('component-b', { /* ... */ })
    Vue.component('component-c', { /* ... */ })
    
    • 局部注册
    new Vue({
      el: '#app',
      components: {
        'component-a': ComponentA,
        'component-b': ComponentB
      }
    })
    

    生命周期

    computed
    • 计算属性,如将总价在computed里计算,从而实时计算商品购物车里的商品总价
    • 对于任何复杂逻辑,你都应当使用计算属性
    • 无变化时,缓存,提高效率;

    语法

    Object.assign

    参数带上这个,参数会被对象化,如果参数是数字或字符串,会被拆分成一个个的数字或字符;

    工具

    同步

    Promise.all
    • 顺序执行进程
    // 假如有三个接口调用动作a1,a2,p5
    Promise.all([a1,a2,p5]).then((result) => {
      console.log(result) // 结果数组或者错误
    }).catch((error) => {
      console.log(error)
    })
    
    Promise.race

    构建类

    • yarn
      可以代替npm的包依赖管理工具

    VueX

    • 在SPA单页面组件的开发中 Vue的vuex和React的Redux 都统称为同一状态管理,个人的理解是全局状态管理更合适;简单的理解就是你在state中定义了一个数据之后,你可以在所在项目中的任何一个组件里进行获取、进行修改,并且你的修改可以得到全局的响应变更.
    • 管理Token、全局个人偏好
    mutations
    const storeLogin = new Vuex.Store({
    
      state: {
        // 存储token
        Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : ''
      },
    	
      mutations: {
        // 修改token,并将token存入localStorage
        changeLogin(state,user) {
          console.log('进入changeLogin')
          state.Authorization = user.Authorization;
          localStorage.setItem('Authorization', user.Authorization);
        }
      }
    });
    

    又如↓

    mutations: {
      increment (state, payload) {
        state.count += payload.amount
      }
    }
    

    mutations下的函数只适合接收一个对象参数,state是默认传入,不能把state当做形参

    rules

    先在html引入rules

    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left">
    

    data里定义rules

    data() {
    		loginRules: {
    		// 验证器 validateUsername 是一种特殊的函数
            username: [{ required: true, trigger: 'blur', validator: validateUsername }],
            password: [{ required: true, trigger: 'blur', validator: validatePassword }]
          },
      },
    

    验证器也在data里面

    const validateUsername = (rule, value, callback) => {
    	// validUsername 是引入的js函数
          if (!validUsername(value)) {
            callback(new Error('请输入正确的用户名'))
          } else {
            callback()
          }
        }
    

    引入validUsername

    import { validUsername } from '@/utils/validate' // 导入js
    

    Element-ui

    // 引入ui
    import ElementUI from 'element-ui' //element-ui的全部组件
    import 'element-ui/lib/theme-chalk/index.css'//element-ui的css
    Vue.use(ElementUI) //使用elementUI
    
    cnpm install [email protected] -S
    
    穿梭框踩坑
    • 效果
      Vuejs学习v1.0.1,持续更新中。。。_第1张图片
    <div style="text-align: left">
              
              <el-transfer
                v-model="value4"
                style="text-align: left; display: inline-block"
                filterable
                :render-content="renderFunc"
                :titles="['用户角色', '已选角色']"
                :button-texts="['放弃', '选择']"
                :format="{
                  noChecked: '${total}',
                  hasChecked: '${checked}/${total}'
                }"
                :data="roleData"
                :props="defaultProps"
                @change="handleChange"
              >
    
    
                <el-button slot="left-footer" class="transfer-footer" size="small">操作el-button>
                <el-button slot="right-footer" class="transfer-footer" size="small">操作el-button>
              el-transfer>
            div>
    
    data() {
        return {
          // 角色数据
          data: [],
          // 别名
          defaultProps: {
            key: 'roleId',
            label: 'roleName',
            disabled: false
          },
          // 不要value4无法移动元素
          value4: [1],
          renderFunc(h, option) {
          // 生成元素的显示名称
            return <span>{ option.roleName }</span>
            // return { option.roleId } - { option.roleName }
          },
    
    • 目标框初始值不出来解决了
      Vuejs学习v1.0.1,持续更新中。。。_第2张图片

    踩坑

    Vuejs学习v1.0.1,持续更新中。。。_第3张图片

    • demo
      cc.vue
    # 新建cc组件
    template>
      <div>
          中国工农红军
          <ul>
              <li v-for="site in sites" :key="site.name">
                  {{site.url}}
                  <a :href="site.url" target="_blank">{{site.name}}a>
              li>
          ul>
          <input type="button" value="点击我" @click="printText"/>
      div>
    template>
    <script>
    // import { METHODS } from 'http'
    export default {
      name: 'Cc',
      methods: {
        clickTest: function () {
          alert('你点击了按钮')
        },
        printText: function () {
          console.log('你点击了按钮')
        }
      },
      data () {
        return {
          msg: '书籍是人类进步的阶梯',
          msg2: 'Apple',
          sites: [
            {url: 'http://router.vuejs.org/', name: 'Jack'},
            {url: 'http://vuex.vuejs.org/', name: 'Tom'},
            {url: 'https://github.com/vuejs/awesome-vue', name: 'Jimy'}
          ]
        }
      }
    }
    script>
    
    
    <style scoped>
    h1, h2 {
      font-weight: normal;
    }
    ul {
      list-style-type: none;
      padding: 0;
    }
    li {
      display: inline-block;
      margin: 0 10px;
    }
    a {
      color: #42b983;
    }
    style>
    
    

    index.js

    # 注入组件
    import cc from '@/components/cc'
    

    错误记录

    简单
    代码不起效
    • 可能浏览器缓存了,没有更新,F12打开调试页面,设置当打开F12时不缓存
    Error in created hook: “TypeError: _admin.menuTree.then is not a function”

    因为导入的函数没加括弧

    // 函数需要括弧,↓对的
    menuTree().then(response => {
    }
    
    请求头类型不对
    // 组件内部增加下述代码 - 局部请求头
    import Axios from 'axios'
    Axios.defaults.headers.post['Content-Type'] = 'application/json'
    # 或者在axios api里加入
    export function call(data) {
      return request({
        headers: {
          'Content-Type': 'application/json' // 设置请求头请求格式为JSON
        },
        url: url,
        method: 'post',
        data
      })
    }
    
    ‘vue-cli-service’ 不是内部或外部命令,也不是可运行的程序
    vue-cli-service serve --mode development # 检查有无安装vue-cli-service serve
    

    检查命令执行后,报错:

    'vue-cli-service' 不是内部或外部命令,也不是可运行的程序
    

    解决办法:将原node_modules重命名,重新执行cnpm run dev即可。因此node_modules最好是每个项目有一个个性化的,比如某些项目某些模块必须npm安装。

    npm相关
    • js内存溢出
    npm install -g increase-memory-limit
    # 进入项目文件夹运行:
    increase-memory-limit
    
    Webpack相关
    • 找不到模块 Can’t find module
     // cmpnt = () => import(`@/views${m_url}`)
    cmpnt = (resolve) => require([`@/views${m_url}`], resolve)
    // 上面的改成下面的
    

    查看原理

    简易功能开发

    ajax

    • axios
    # install axios
    cnpm install axios --save-dev
    # --save-dev以省掉手动修改package.json文件的步骤
    

    axios发送ajax请求

    <script src="/js/axios.min.js"></script>
    	window.onload=function(){
                new Vue({
                    el:'#app',
                    data:{
                        users:{
                            name:'',
                            age:''
                        }
                    },
                    methods:{
                        sendPsot(){
                            axios.post('post.php', {
                                name: this.users.name,
                                age: this.users.age,
                              })
                              .then(function (response) {
                                console.log(response);
                              })
                              .catch(function (error) {
                                console.log(error);
                              });
                        }
                        
                    }
                });
            }
    
    • 跨域配置
      后端亦可配置跨域,前后端不要都配
      config/index.js,proxyTable里增加内容(老式)
    proxyTable: {
          // 解决跨域
          '/tbapi':{
            // target: "http://api.douban.com/v2",
            target: "https://suggest.taobao.com",
            changeOrigin:true,
            pathRewrite:{
              '^/tbapi':''
            }
          }
    
        },
    

    组件js

    // 跨域
      Axios.defaults.baseURL = '/tbapi'
      Axios.defaults.headers.post['Content-Type'] = 'application/json'
    
    mounted() {
          //GET
          this.$ajax({
            method: 'get',
            // tbapi会代替localhost
            url: '/sug?code=utf-8&q=电冰箱',
            // url: '/sug?code=utf-8&q=电冰箱&callback=cb',
          }).then(response => {
            // response包含config data等
            var resData = response.data.result
            iceBoxes = resData
            resData.forEach(item => {
              console.log(item[0])
              console.log(item[1])
              // console.log('数据序号'+i+'=='+item)
            })
    
          }).catch(function (err) {
            console.log(err)
          })
    
          //POST
          this.$ajax({
            method: 'post',
            url: '/sug?code=utf-8&q=iPhone',
            // data: {
            //   code: 'utf-8',
            //   q: 'iPhone'
            // }
          }).then(response => {
            // response包含config data等
            var resData = response.data.result
            resData.forEach(item => {
              console.log(item[0])
              console.log(item[1])
              // console.log('数据序号'+i+'=='+item)
            })
          }).catch(function (err) {
            console.log(err)
          });
        }
    

    登录

    逻辑
    handleLogin() {
          this.$refs.loginForm.validate(valid => {
            if (valid) {
              this.loading = true
              // 也就是说,全局store对象拥有原生的dispatch方法,用于请求API
              this.$store.dispatch('user/login', this.loginForm).then(() => {
                this.$router.push({ path: this.redirect || '/' })
                this.loading = false
              }).catch(() => {
                this.loading = false
              })
            } else {
              console.log('error submit!!')
              return false
            }
          })
        }
    
    源码解析

    上文的$store来自这里@/store/index.js,片段↓

    const store = new Vuex.Store({
      modules,
      getters
    })
    // 导出实例this的store属性
    export default store
    

    store的dispatch,该单词是发送的意思
    axios的配置

    // request即service-axios
    import request from '@/utils/request'
    
    // ↑request含有拦截器,url改为合适的baseUrl
    export function login(data) {
      console.log('axios实例==',request)
      return request({
        url: '/user/login',
        method: 'post',
        data
      })
    }
    

    当index.vue里的store.dispatch执行请求时,即会找到上面的login函数,由login函数发出调用请求,接着,我们看request.js里的代码

    import axios from 'axios'
    import { MessageBox, Message } from 'element-ui'
    import store from '@/store'
    import { getToken } from '@/utils/auth'
    // global全局配置
    import { baseUrl } from '@/utils/global'
    // create an axios instance 没错,service就是axios实例,import@/utils/request即注入service-axios实例
    const service = axios.create({
      baseURL: baseUrl, // url = base url + request url
      // baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
      // withCredentials: true, // send cookies when cross-domain requests
      timeout: 5000 // request timeout
    })
    

    ↑这是axios的配置,配置了url和超时时间,当执行$store.dispatch时,即会加上baseUrl进行请求。
    接收响应↓,也来自request.js

    response => {
    
        const res = response.data
    
        // if the custom code is not 20000, it is judged as an error.
        if (res.code !== 20000) {
          Message({
            message: res.message || 'Error',
            type: 'error',
            duration: 5 * 1000
          })
    
          // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
          if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
            // to re-login
            MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
              confirmButtonText: 'Re-Login',
              cancelButtonText: 'Cancel',
              type: 'warning'
            }).then(() => {
              store.dispatch('user/resetToken').then(() => {
                location.reload()
              })
            })
          }
          return Promise.reject(new Error(res.message || 'Error'))
        } else {
          return res
        }
      },
    
    后台API切换回Mock需改动
    // 1 request.js
    const service = axios.create({
      // baseURL: baseUrl, // url = base url + request url
      baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
      // withCredentials: true, // send cookies when cross-domain requests
      timeout: 5000 // request timeout
    })
    // 2 user.js
    // params: data
        data
    // 3 user.js
    // method: 'post',
        method: 'get',
    

    Token - 令牌

    • vuex的…mapMutations([’
    • 前后端属于不同的域,导致每次ajax请求服务器都会当做新的用户访问,导致session丢失。当然也可以通过维护cookie来让服务端辨识客户端,如axios.defaults.withCredentials=true;
    • 每次请求被认为是新客户端,产生新session问题,注意session的膨胀;

    全局变量

    引入模板

    弹窗
    • 新建模板Test.vue
    • 使用
    import TestMode from './Test' // 导入相对路径的Test.vue
    ...
    components: { TestMode }, // 注册组件
    
    <test-mode v-if="testPageVisible" ref="testMode2" @refreshDataList="getList">test-mode>
    
    test(row) { // 这里是父组件
         this.testPageVisible = true
         this.$nextTick(() => {
           this.$refs.testMode2.test(Object.assign({}, row)) // 调用子组件的函数 testMode2
         })
       },
    
    methods: {
        test() { // 这里是Test.vue组件·················
          this.dialogFormVisible = true
        }
      }
    

    编辑器

    Json编辑器

    原版↓很糟糕
    在这里插入图片描述
    使用json组件解决,待续

    布局

    九宫格|十六宫格

    事态紧急跨域用表格代替九宫格,不过不正规。
    正规的待续

    兼容

    解决IE兼容问题

    • promise
    # 安装es6-promise
    npm install es6-promise --save-dev
    
    • main.js中引入ES6的polyfill
    import Es6Promise from 'es6-promise'
    Es6Promise.polyfill();
    

    上文安装的promise只是针对性的,要彻底兼容IE还需要研究。

    你可能感兴趣的:(修道)