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


版本号 作者 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)]
    <!--PS: router-view渲染路由信息于此-->
    <router-view/>
  </div>
</template>

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

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

组件

概念

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: '<li>这是个待办项</li>'
    })
  • .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

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

工具

构建类

  • 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
穿梭框踩坑
  • 效果
    在这里插入图片描述
<div style="text-align: left">
          <!-- <div style="text-align: center"> -->
          <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"
          >
<!--            ↓等效:render-content-->
<!--            <span slot-scope="{ option }">{{ option.roleId }} - {{ option.roleName }}</span>-->
            <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 <span>{ option.roleId } - { option.roleName }</span>
      },
  • 目标框初始值不出来解决了
    在这里插入图片描述

踩坑

*

  • 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>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<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'

错误记录

简单
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安装。

简易功能开发

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><!-- 使用组件.getList是确定后的执行函数-->
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还需要研究。

发布了49 篇原创文章 · 获赞 6 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/cc007cc009/article/details/92764232