Vue.js 入门

学习来源:Bilibili 遇见狂神说 Vue最新快速上手教程通俗易懂

MVVM (Model-View-ViewModel) 思想

为什么要使用 MVVM

MVVM 模式和 MVC 模式一样,主要目的是分离视图(View)和模型(Model),有几大好处:

  • 低耦合:视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变;
  • 可复用:你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑;
  • 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计;
  • 可测试:界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。

Vue 实例

数据的基本引用

<div id="app">
  <p>
    {{message}}
  </p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      message: "123",
    }
  });
</script>

v-bind

v-bind 用于绑定数据和元素属性 缩写为 :

<div id="app">
  {{student}}
  <span v-bind:title="message">
    鼠标悬停
  </span>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      message: "hello world",
      student: "张三",
    }
  });
</script>

v-on

v-on 调用函数 缩写为 @

<div id="app">
  <button v-on:click="sayHi()">click me</button>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      message: "啦啦啦",
    },
    methods: {
      sayHi: function () {
        alert(this.message);
      }
    }
  });
</script>

v-model

v-model 双向数据绑定(表单数据监听)

<div id="app">
  输入的文本:<input type="text" v-model="message"> {{message}}
  性别:
  <input type="radio" name="sex" value="man" v-model="sexChecked"> man
  <input type="radio" name="sex" value="woman" v-model="sexChecked"> woman
  <p>选中了谁:{{sexChecked}}</p>
  <select v-model="letter">
    <option value="" disabled>--请选择--</option>
    <option value="A">A</option>
    <option value="B">B</option>
    <option value="C">C</option>
  </select>
  <p> {{letter}} </p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      message: "123",
      sexChecked: 'man',
      letter: 'B',
    }
  });
</script>

Vue 的生命周期

Vue 实例生命周期
生命周期钩子 组件状态 最佳实践
beforeCreate 实例初始化之后,this 指向创建的实例,不能访问到 data、computed、watch、methods 上的方法和数据 常用于初始化非响应式变量
created 实例创建完成,可访问 data、computed、watch、methods 上的方法和数据,未挂载到 DOM,不能访问到 $el 属性,$ref 属性内容为空数组 常用于简单的 ajax 请求,页面的初始化
beforeMount 在挂载开始之前被调用,beforeMount 之前,会找到对应的 template,并编译成 render 函数
mounted 实例挂载到 DOM 上,此时可以通过 DOM API 获取到 DOM 节点,$ref 属性可以访问 常用于获取 VNode 信息和操作,ajax 请求
beforeupdate 响应式数据更新时调用,发生在虚拟 DOM 打补丁之前 适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器
updated 虚拟 DOM 重新渲染和打补丁之后调用,组件 DOM 已经更新,可执行依赖于 DOM 的操作 避免在这个钩子函数中操作数据,可能陷入死循环
beforeDestroy 实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例 常用于销毁定时器、解绑全局事件、销毁插件对象等操作
destroyed 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁

注意:

  1. created 阶段的 ajax 请求与 mounted 请求的区别:前者页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态
  2. mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染

参考文章:

详解 Vue 生命周期

Vue 生命周期深入

axios 请求框架

npm install –save axios vue-axios

HTML 内使用

<div id="vue" v-clock>
  {{info.name}}
  <p>{{info.address.city}}</p>
  <p>{{info.address.street}}</p>
</div>

// CDN 链接
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

<script>
  var vm = new Vue({
    el: "#vue",
    data: {
      info: {}
    },
    mounted() { // 钩子函数 链式编程
      axios.get('./data.json').then(res => (this.info = res.data))
    }
  });
</script>

发送单个请求

// axios 发送单个请求
axios({
  url: 'http://127.0.0.1:8080/home/multidata',
  methods: 'get'
}).then(res => {
  console.log(res);
})

axios({
  url: 'http://127.0.0.1:8080/home/data',
  methods: 'get',
  params: {
    type: 'pop',
    page: 1
  }
}).then(res => {
  console.log(res);
})

发送并发请求

// axios 发送并发请求
axios.all([
  axios({
    url: 'http://127.0.0.1:8080/home/multidata',
  }),

  axios({
    url: 'http://127.0.0.1:8080/home/data',
    methods: 'get',
    params: {
      type: 'pop',
      page: 1
    }
  })
]).then(axios.spread ((res1, res2) => {
  console.log(res1);
  console.log(res2);
}));

axios全局配置

axios.defaults.baseURL = 'http://127.0.0.1:8080';
axios.defaults.timeout = 5000;

axios 实例

// 创建对应的 axios 的实例
const instance1 = axios.create({
  baseURL = 'http://127.0.0.1:8080',
  timeout = 5000
});

instance1({
  url: '/home/multidata'
}).then(res => {
  console.log(res);
})

instance1({
  url: '/home/data',
  params: {
    type: 'pop',
    page: 1
  }
}).then(res => {
  console.log(res);
})

axios拦截器(请求成功,请求失败,响应成功,响应失败)

export function request(config) {
  // 1.创建axios的实例
  const instance = axios.create({
    baseURL: 'http://123.207.32.32:8000',
    timeout: 5000
  })
  // 2.axios的拦截器
  // 2.1请求拦截的作用
  instance.interceptors.request.use(config => {
    console.log(config);
    // 1.比如config中的一些配置信息不符合服务器要求
    // 2.比如每次发送网络请求时,都希望在页面中显示一个请求的图标
    // 3.某些网络请求(比如登陆(token)), 需要携带一些特殊的信息

    return config
  }, err => {
    // 断网的时候(发不出去)
    console.log(err);
  });

  // 2.2响应拦截的作用
  instance.interceptors.response.use(res => {
    console.log(res);
    return res.data;
  }, err => {
    console.log(err);
  });

  // 3.发送真正的网络请求
  return instance(config);
}

Component 组件

<div id="app">
  <liu v-for="item in items" v-bind:title="item">
  </liu>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  // 定义一个Vue组件component
  Vue.component("liu", {
    props: ['title'],
    template: '<li>{{title}}</li>',
  })

  var vm = new Vue({
    el: "#app",
    data: {
      items: ["Java", "Linux", "前端"],
    }
  });
</script>

slot 插槽

<div id="app">
  <!-- 自定义事件内容分发 -->
  <todo>
    <todo-title slot="todo-title" :title="title"></todo-title>
    <todo-items slot="todo-items" v-for="(todoItem, index) in todoItems"
                :item="todoItem" @remove="removeItem(index)"></todo-items>
  </todo>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>

  // 定义一个Vue组件component
  Vue.component("todo", {
    template:
    `<div>
<slot name="todo-title"></slot>
<ul>
<slot name="todo-items"></slot>
  </ul>
  </div>`
  });

  Vue.component("todo-title", {
    props: ['title'],
    template: '<div>{{title}}</div>'
  });

  Vue.component("todo-items", {
    props: ['item'],
    template: '<li>{{item}}<button @click="remove">delete</button></li>',
    methods: {
      remove: function () {
        this.$emit('remove');
      }
    }
  });

  var vm = new Vue({
    el: "#app",
    data: {
      title: "liubihao123",
      todoItems: ["Java", "Linux", "前端"],
    },
    methods: {
      removeItem: function (index) {
        this.todoItems.splice(index, 1);
      }
    }
  });
</script>

创建一个Vue项目并启动

Vue Cli 2 初始化项目:

> vue init webpack myvue
> cd myvue
> npm install
> npm audit fix
> npm run dev

Vue Cli 3 初始化项目:

> vue create myvue
> ...

vue-router

npm install vue-router –save-dev

官方文档:https://router.vuejs.org/zh/

Webpack

安装 Webpack

npm install webpack -g

npm install webpack-cli -g

测试安装成功

webpack -v

webpack-cli -v

webpack.base.conf.js

image-20200712204709128 image-20200712204809521

使用 Webpack

  1. 创建项目

  2. 创建一个名为 modules 的目录,用于放置 JS 模块等资源文件

  3. 在 modules 下创建模块文件,如 hello.js,用于编写 JS 模块相关代码

exports.sayHi = function () {
  document.write("<div>Hello Webpack</div>");
};
  1. 在 modules 下创建一个名为 main.js 的入口文件,用于打包时设置 entry 属性

    var hello = require("./hello");
    hello.sayHi();
  2. 在项目目录下创建 webpack.config.js 配置文件,使用 webpack 命令打包

    modules.exports = {
      entry: "./modules/main.js",
      output: {
        filename: "./js/bundle.js"
      }
    };
  3. ```html

    
    ```shell
    # 参数 --watch 用于监听变化
    webpack --watch

Tips

v-clock 解决闪烁问题

<!--    v-clock 解决闪烁问题-->
<style>
  [v-clock] {
    display: none;
  }
</style>

HTML5 History 模式

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

建立公用方法

  1. 新建 ./utils/common.js

    // 全局变量
    const globalObj = {};
    
    // 定义公共变量
    globalObj.name = 'LPxz';
    
    // 定义公共方法
    globalObj.toWebsite = function (url) {
      window.location.href = url;
    };
    
    globalObj.reloadPage = function () {
      window.location.reload();
    };
    
    export default globalObj
  2. main.js 中引用

    import commonMethods from './utils/common'
    
    Vue.prototype.$common = commonMethods;
  3. Vue 文件调用

    <template>
    <div id="lp_header">
      <img src="static/images/logo_LPxz.png" class="logo" alt="" @click="reloadPage()">
      <el-menu-item index="3" @click="toWebsite('https://github.com/LiuPiPiPi')">Github</el-menu-item>
      </div>
    </template>
    
    <script>
      export default {
        name: "lp_header",
        methods: {
          handleSelect(key, keyPath) {
            console.log(key, keyPath);
          },
          reloadPage() {
            this.$common.reloadPage();
          },
          toWebsite(url) {
            this.$common.toWebsite(url);
          }
        },
      }
    </script>
a {
  text-decoration: none;
}

Vue 路由验证

推荐文章:https://juejin.im/post/6844903916870565901

el-input @change 无法触发的问题

推荐文章:https://www.jianshu.com/p/18fa34a3efcb

Vue 修改对象的属性值后页面不重新渲染

HTML页面如下:

<template  v-for="item in tableData">
  <div :class="{'redBorder':item.red}">
    <div>{{ item.name}}</div>
    <div>
      <el-button size="mini" @click="clickBtn(item.id)" type="info">编辑</el-button>
      <p class="el-icon-error" v-show="item.tip"></p>
    </div>
  </div>
</template>

js 部分如下:

<script>
  export default {
    data() {
      return {
        tableData:[{id:0,name:"lili",red:false,tip:false}]
      }
    },
    methods: {
      clickBtn(id){
        this.tableData[id].red=true;
        this.tableData[id].tip=true;		
      }
    }
  }
</script>

绑定的 class 是加一个红色的边框,如下:

.redBorder {
  border:1px solid #f00;
}

在项目中点击 button 后不出现红色边框和提示错误框,打开 debugger 查看,发现运行到了这里却没有执行,tableData 中的值并没有改变,这个方法在以前使用时会起作用,可能是这次的项目比较复杂引起的,具体原因不明。
后通过查找资料修改为使用 $set 来设定修改值,JS 如下:

this.$set(this.tableData[id], "red", true);

但是依然没有起作用,打开 debugger 发现 tableData 的值修改成功,没有渲染到页面上,查找的资料也是比较凌乱,并不能解决问题,后请教大神,才知道是数据层次太多,没有触发 render 函数进行自动更新,需手动调用,调用方式如下:

this.$forceUpdate();

JS 完整代码如下:

<script>
  export default {
    data() {
      return {
        tableData:[{id:0,name:"lili",red:false,tip:false}]
      }
    },
    methods: {
      clickBtn(id){
        this.$forceUpdate();
        this.$set(this.tableData[id],"red",true);
        this.$set(this.tableData[id],"tip",true);	
      }}}
</script>

element-ui 中拿到表格当前行的索引

scope.$index 可取到当前索引

<el-table-column label="操作">
  <template slot-scope="scope">
    <el-button size="small" v-if="scope.row.auth_info.status===110"
               @click="disabledUser(scope.row.id,scope.$index)">
      封禁</el-button>
    <el-button size="small" type="danger" v-else
               @click="disabledUser(scope.row.id,scope.$index)">
      解禁</el-button>
  </template>
</el-table-column>

v-for 渲染页面获取不到 DOM 元素解决方案

HTML 代码

<div id="app">
  <li v-for="item in abc" :name="{{item.name}}">{{item.age}}</li>
</div>

JS 代码(错误代码)

// 错误代码示例一
var vm = new Vue({
  el: '#app',
  data: {
    abc: new Object()
  },
  mounted: function () {
    this.getList();
    var li = document.querySelectorAll('li');
    console.log(li.length);     // 输出0
  },
  methods: {
    getList: function () {
      var data = [
        { name: '1', age: '21' },
        { name: '2', age: '22' },
        { name: '3', age: '23' },
        { name: '4', age: '24' },
        { name: '5', age: '25' }
      ];
      this.$set(this, 'abc', data);
    }
  }
}) 
// 错误代码示例二
var vm;
window.onload = function(){
  vm = { ... } // 实例化代码如上,去除 mounted 生命周期
        vm.getList();
  var li = document.querySelectorAll('li');
  console.log(li.length);     // 输出0
}

上述代码中的 li 即是通过 v-for 渲染生成,但是两种方式获取 li 输出的长度都为 0。

最终发现将数据初始化放到 beforeMount 里面即可在 mounted 里面正确输出获取到 li 元素的长度。

JS 代码(解决方案)

// 解决方案一(数据初始化放在挂载之前)
var vm = new Vue({
    el: '#app',
    data: {
        abc: new Object()
    },
    beforeMount: function(){
         this.getList();
    },
    mounted: function () {
         var li = document.querySelectorAll('li');
         console.log(li.length)     // 输出5
    },
    methods: {
        getList: function () {
             var data = [
                 { name: '1', age: '21' },
                 { name: '2', age: '22' },
                 { name: '3', age: '23' },
                 { name: '4', age: '24' },
                 { name: '5', age: '25' }
             ];
             this.$set(this, 'abc', data);
       }
    }
}) 

// 解决方案二(mounted周期里面使用VUE自带钩子函数$nextTick做处理)
mounted: function () {
    this.getList();
    this.$nextTick(function () {
          var li = document.querySelectorAll('li');
          console.log(li.length)  // 输出 5
    })
}

Vue.js 入门
http://lpxz.work/posts/48124/
作者
LPxz
发布于
2021年12月29日
许可协议