vue

vue文档


一、课程介绍

https://vuejs.lipengzhou.com/

内容

使用 Vue.js 系列技术栈进行网站应用开发

具体知识点:

  • ECMAScript 6
  • Vue.js
  • Vue Router
  • Vuex
  • Ajax
  • Vue CLI
  • axios
  • webpack
  • Vue SSR
  • RESTful

前置知识

  • HTML
  • CSS
  • JavaScript
  • 基本的 ECMAScript 6 语法
  • 基本的 Ajax 使用
  • node命令行操作
  • npm命令
  • 基本的 Git 操作

二、项目实战

卖座移动端Vue

  • 使用vue-cli脚手架搭建项目

  • 导入有赞UI组件和使用

  • 字体图标使用

  • 搭建底部路由切换部分

  • vue路由切换总是默认添加一个class router-link-exact-active

三、ES6常用的基础知识点

ES6介绍

ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了也叫ECMAScript 2015。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言

let 和 const 命令

let

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

{
  let a = 10;
  var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

for (let i = 0; i < 10; i++) {
  // ...
}
console.log(i);
// ReferenceError: i is not defined

const 和let不存在变量提升

const

const声明一个只读的常量。一旦声明,常量的值就不能改变。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

-------------------------------------------------
const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

变量的解构赋值

数组的解构

let a = 1;
let b = 2;
let c = 3;

ES6 允许写成下面这样。
let [a, b, c] = [1, 2, 3];

对象的解构赋值

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

--------------------------------------------
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

模板字符串

let place = "world"
// 变量place没有声明
let msg = `Hello, ${place}`;

对象的扩展

属性的简洁表示法

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};

--------------------------------------
function f(x, y) {
  return {x, y};
}

// 等同于

function f(x, y) {
  return {x: x, y: y};
}

f(1, 2) // Object {x: 1, y: 2}

除了属性简写,方法也可以简写。

const o = {
  method() {
    return "Hello!";
  }
};
// 等同于
const o = {
  method: function() {
    return "Hello!";
  }
};

------------------------------------------
let birth = '2000/01/01';
const Person = {
  name: '张三',
  //等同于birth: birth
  birth,
  // 等同于hello: function ()...
  hello() { console.log('我的名字是', this.name); }
};

函数的扩展

函数的扩展 ES6 允许使用“箭头”(=>)定义函数。

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

var sum = (num1, num2) => { return num1 + num2; }

箭头函数有几个使用注意点

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

基本用法 ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。下面代码创造了一个Promise实例。

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

错误捕获

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

promise.all() promise.race()

async/ awite

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。async 函数是什么?一句话,它就是 Generator 函数的语法糖。

基本用法

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);
上面代码指定 50 毫秒以后,输出hello world。

返回 Promise 对象

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

第 1 章 Vue.js 介绍

前端技术发展历程

  • 前端开发的三个时代
    • 纯原生 JavaScript
    • jQuery(仅仅是提高了 DOM 操作的效率)
    • 框架时代
  • 前端交互越来越多,功能需求越来越复杂;旧浏览器逐渐淘汰,移动端需求增加
  • 架构由传统的后台 MVC 向 REST API + MV* 发展
  • 前后端分离开发方式
  • 各种前端框架诞生(vue,react,angular)

Vue是什么

以下引自官网原话:

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

  • 一款非常优秀的前端渐进式 JavaScript 框架
    • Vue 本身只是一个用于数据驱动视图更新的库
    • 随着生态的慢慢发展,如今已经有了 Vue Router、Vuex、Vue CLI、Vue Server Renderer 等功能库,所以当 Vue 和这些核心功能库结合到一起的时候,我们称之为一个框架
    • 这些技术一般也称之为 Vue 全家桶,所以学习 Vue 实际上就是要学习 Vue 全家桶才能发挥出 Vue 的威力
  • 由尤雨溪开发并于 2014 年 2 月开源于 Github(14w+ ⭐️)
  • 可以轻松构建 SPA 应用程序
  • 通过 指令 扩展了 HTML,通过 表达式 绑定数据到 HTML,通过组件化开发极大的提高了开发的效率和可维护性
  • 最大程度上解放了 DOM 操作

发展历史

Github 发布记录

  • Vue.js 由尤雨溪个人正式发布于 2014 年 2 月,并开源于 Github
  • 2015 年 10 月 27 日,正式发布 1.0
  • 2016 年 8 月 1 日,正式发布 2.0
  • 截止到 2019-6 目前最新版本为 2.6.10
  • 预计今年(2019)会发布 3.0 版本
  • 目前已在 Github 收获 14w+ Star

Vue 核心思想

Vue 是为了克服 HTML 在构建应用上的不足而设计的。Vue 有着诸多特性,最为核心的是:

  • 数据驱动
    • DOM 是数据的一种自然映射
    • 数据改变自动驱动视图更新
      mvvm.png
  • 组件化
    • 扩展 HTML 元素,封装可重用代码
      组件化.png

相关链接

关于作者

尤雨溪.jpg

第 2 章 Vue 基础

Vue.js 不支持 IE8 及其以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器。最新稳定版本:2.6.10

安装

  • 直接下载
  • CDN
    • <script src="https://cdn.jsdelivr.net/npm/vue"></script> 最新稳定版
    • <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script> 指定版本
  • 使用 npm 下载(最好创建一个 package.json 文件,用来存储第三方包依赖信息)
    • npm install vue 最新稳定版
      • npm install vue@版本号 指定版本

创建了第一个 Vue 应用

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <h1>{{ message }}</h1>
  </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue.js!'
      }
    })
  </script>
</body>
</html>

声明式渲染

需求:下面的数据如何显示到页面中?

const data = {
  message: 'Hello Vue.js!',
  user: {
    name: '张三',
    age: 18,
    gender: 0
  }
}
------------------------------------
<h1></h1>
<p>姓名:</p>
<p>年龄:</p>
<p>性别:</p>

方式一:操作 DOM

方式二:模板引擎

方式三:使用 Vue

<body>
    <div id="app">
        <h1>{{message}}</h1>
        <p>姓名:{{user.name}}</p>
        <p>年龄:{{user.age}}</p>
        <p>性别:{{user.gender}}</p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                message: 'Hello Vue.js!',
                user: {
                    name: '张三',
                    age: 18,
                    gender: 0
                }
            }
        })
    </script>
</body>

实例选项 - el

  • 类型:string | Element:
  • 详细: 提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTMLElement 实例。在实例挂载之后,元素可以用 vm.$el 访问

el 不能是 html、body 节点

实例选项 - data

  • 类型:Object | Function
new Vue({
    el: '#app',
    data(){
        return {message:"demo"}
    }
})
---------------------------
new Vue({
    el: '#app',
    data:{message:"demo"}
})

组件的定义只接受 function,模板中访问的数据必须初始化到 data 中

模板语法 - 插值{{}}

  • 文本

    <p>{{ message }}</p>
    <span>{{ message }}</span>
    <strong>{{ message }}</strong>
    
  • JavaScript 表达式

    <p>{{ number + 1 }}</p>
    <p>{{ number + 1 > 10 ? 'number大于10' : 'number小于10' }}</p>
    <p>{{ arr }}</p>
    <p>{{ message.split('').reverse().join('') }}</p>
    

模板语法 - 指令

指令 (Directives) 是带有 v- 前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)

常用指令:

  • v-if:

    v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值的时候被渲染。
    <h1 v-if="awesome">Vue is awesome!</h1>
    
  • v-else

    <div v-if="Math.random() > 0.5">
    Now you see me
    </div>
    <div v-else>
    Now you don't
    </div>
    
  • v-else-if

    <div v-if="type === 'A'">
    A
    </div>
    <div v-else-if="type === 'B'">
    B
    </div>
    <div v-else-if="type === 'C'">
    C
    </div>
    <div v-else>
    Not A/B/C
    </div>
    
  • v-show

    <h1 v-show="ok">Hello!</h1>
    
  • v-text

    <div v-text="text"></div> // 带标签的内容不会被解析
    
  • v-html

    <div v-text="text"></div> // 带标签的内容会被解析
    
  • v-model(表单输入绑定)

    <input v-model="test"/> //这个指令是一个语法糖,一般只用于input标签
    
  • v-for (循环指令,后面讲)

  • v-bind (绑定指令,后面讲)

  • v-on (监听指令)

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好

列表渲染

  • v-for="item in todos"
  • v-for="(item, index) in todos"

数组数据渲染

对象数据渲染

字符串数据渲染

绑定指令v-bind

绑定指令能绑定我们元素上的一些属性,eg:<a v-bind:href="url">...</a> v-bind 指令将该元素的 href 特性与变量 url 的值绑定。

绑定指令能绑定元素上的任何属性,常用的比如 id, style, class, src, ... 也能绑定自定义属性(父子传参使用,到时候再讲)

操作元素的 class 列表和内联样式是数据绑定的一个常见需求,因为它们都是属性,所以我们可以用 v-bind 处理它们,在将 v-bind 用于 class 和 style 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组

Class 绑定

  • 一般使用

    // class的值就是变量className的值
    <div v-bind:class="className"></div> 
    <div v-bind:class="className?'a':'b'"></div> 
    
  • 对象语法

// 如果变量isActive 值为真则有active这个类,否则没有
<div v-bind:class="{ active: isActive }"></div>
-----------------------------------------------
数据:
data: {
  isActive: true
},
-----------------------------------------------
渲染为:
<div class="active"></div>
  • 数组语法(使用偏少)
<div v-bind:class="[activeClass, errorClass]"></div>
---------------------------------------------------
数据:
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
-----------------------------------------------
渲染为:
<div class="active text-danger"></div>

Style 绑定

  • 对象语法
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
-----------------------------
数据:
data: {
  activeColor: 'red',
  fontSize: 30
}
-----------------------------
直接绑定到一个样式对象通常更好,这会让模板更清晰:
<div v-bind:style="styleObject"></div>
data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}
  • 数组语法(很少用,不介绍)

缩写

<!-- 完整语法 -->
<a v-bind:href="url">...</a>

<!-- 缩写 -->
<a :href="url">...</a>

监听指令v-on

监听事件

可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

<div id="example-1">
  <button v-on:click="counter += 1">Add 1</button>
  <p>The button above has been clicked {{ counter }} times.</p>
</div>

var example1 = new Vue({
  el: '#example-1',
  data: {
    counter: 0
  }
})

事件处理方法(常用写法)

许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。因此 v-on 还可以接收一个需要调用的方法名称

<div id="example-2">
  <!-- `greet` 是在下面定义的方法名 -->
  <button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
  el: '#example-2',
  data: {
    name: 'Vue.js'
  },
  // 在 `methods` 对象中定义方法
  methods: {
    greet: function (event) {
      // `this` 在方法里指向当前 Vue 实例
      alert('Hello ' + this.name + '!')
      // `event` 是原生 DOM 事件
      if (event) {
        alert(event.target.tagName)
      }
    }
  }
})

事件传参

<div id="example-3">
  <button v-on:click="say('hi')">Say hi</button>
  <!-- DOM的原生事件,可以用特殊变量 $event 把它传入方法 -->
  <button v-on:click="say('what',$event)">Say what</button>
</div>
new Vue({
  el: '#example-3',
  methods: {
    say: function (message,ev) {
      alert(message)
      console.log(ev)
    }
  }
})

v-on 缩写

<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

练习题

以下是几个小练习,用来辅助大家增强体会 Vue 的数据驱动视图思想(MVVM):

1、姓名展示:两个文本框,让用户分别输入性和名,然后将数据实时展示到界面上 2、数字自动增长:一个文本框用来呈递数字,一个按钮,用户点击按钮,文本框中的数字+1 3、购物车计价器:有商品价格,和商品数量,用户可以点击加减按钮改变商品数量,将价格实时展示到界面上 4、数字计算器:实现加法计算器 5、数字计算器:实现加减乘除 以上所有练习,都不要着急写代码,不要用以前 DOM 的思想去操作, 利用 Vue 的数据驱动视图的思想,去考虑问题:

练习todoList(简单版本)

todolist.gif
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        ul li {
            list-style: none;
        }
        .hasDone{
            text-decoration: line-through;
        }
    </style>
</head>
<body>
    <div id="app">
        <h1>TODO</h1>
        <div>
            <ul>
                <li v-for="item in todoList">
                    <input type="checkbox" @click="change(item)">
                    <span :class="{hasDone: item.complete}">{{item.describe}}</span>
                </li>
            </ul>
        </div>
        <div>
            <input type="text" v-model="todoText" placeholder="add new todo here">
            <button @click="addTodo">Add</button>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
    <script>
        new Vue({
            el: "#app",
            data() {
                return {
                    todoList: [{
                            describe: "the one something todo",
                            complete: false
                        },
                        {
                            describe: "the two something todo",
                            complete: false
                        }
                    ],
                    todoText: ''
                }
            },
            methods: {
                addTodo() {
                    if (this.todoText) {
                        this.todoList.push({
                            describe: this.todoText,
                            complete: false
                        })
                    }
                },
                change(todo){
                    todo.complete = !todo.complete
                }
            },
        })
    </script>
</body>

计算属性(computed)

  • 计算属性是根据源数据衍生出来的新数据,既然是衍生的数据,那只要源数据在data中声明过即可,同时,计算属性的试图更新必须依赖于data属性的更新,此外衍生出来的新数据不能在data中有定义
  • 计算属性的名称就是计算属性处理函数的名称,使用时可以在模版中绑定计算属性,绑定方法与data中的普通属性一致
  • 计算结果并返回,只有当被计算的值发生改变时才会触发
// 基础用法
data() {
    return {
        str: 'string'
    }
}
computed: {
    beautifyStr() {
        return this.str.split('');
    }
}

监听器(watch)

对data属性的监听,说明属性是在data中声明过的属性更新时调用监听函数,可选参数分别为新值和旧值,对属性重新设置值只要跟原来的值相等就不会触发函数调用,这一点跟计算属性是相似的,监听某一个值,当被监听的值发生变化时,执行对应的操作,与computed的区别是,watch更加适用于监听某一个值的变化并做对应的操作,比如请求后台接口等,而computed适用于计算已有的值并返回结果

// 基础用法
new Vue({
    el: '#id',
    data: {
        firstName: 'Leo',
        lastName: 'Alan',
        obj1: {
            a: 0
        }
    },
    watch: {
        // 监听firstName,当firstName发生变化时就会执行该函数
        firstName (newName, oldName) {
            // 执行需要的操作...
            // 注:初始化不会执行,只有当被监听的值(firstName)发生变化时才会执行
        },

        // 监听lastName
        lastName: {
            handler (newName, oldName) {
                // 执行需要的操作...
            },
            immediate: true // true: 初始化时就会先执行一遍该监听对应的操作    
        },

        obj1: {
            handler () {
                // 执行需要的操作...
            },
            deep: true // 该属性默认值为false. 
            // 当被监听的值是对象,只有deep为true时,对应属性的值(obj1.a)发生变化时才能触发监听事件,但是这样非常消耗性能
        },

        // 监听对象具体的属性, deep就不需要设置为true了
        'obj1.a': {
            handler () {
                // 执行需要的操作...
            }
        }
    }
})

加强版todoList

计算未做完的事
todolistPlus.gif
watch: {
    todoList:{
        handler(){
            var count = 0
            this.todoList.forEach(item=>{
                    if(!item.complete){
                        count +=1
                    }
            })
            this.count = count
        },
        immediate:true,
        deep:true
    }
},

第三章案例介绍

案例基本会涵盖到以下内容知识点:

  • 数据绑定
  • 计算属性 computed
  • 侦听器 watch
  • Class 与 Style 样式处理
  • 条件渲染
  • 列表渲染
  • 事件处理
  • 表单输入双向数据绑定

案例介绍

项目初始化

下载模板

1、mkdir todomvc-vue
2、cd todomvc-vue 3、git clone https://github.com/tastejs/todomvc-app-template.git
4、npm install
5、安装完成,打开 index.html 预览模板。
6、导入vue的js文件
7、开始项目功能开发

添加功能

  • 回车健添加到任务列表中
  • 不允许有非空数据
  • 添加完清空输入框内的文本

任务项操作

  • 点击复选框完成todo,
  • 点击删除实现删除功能
  • 点击全选/取消 完成全选/取消功能
  • 监控是否全选,是否非全选显示全选是否选中

底部按钮的操作和显示

  • 显示当前多少项未完成
  • all/active/complete 三个面板切换
  • 清除已经完成的项
  • 编辑、修改title

完整代码

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Template • TodoMVC</title>
        <link rel="stylesheet" href="node_modules/todomvc-common/base.css">
        <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
        <!-- CSS overrides - remove if you don't need it -->
        <link rel="stylesheet" href="css/app.css">
    </head>
    <body>
        <section class="todoapp">
            <header class="header">
                <h1>todos</h1>
                <input class="new-todo" placeholder="What needs to be done?" autofocus v-model="needTodoText" v-on:keyup.enter="addTodo">
            </header>
            <!-- This section should be hidden by default and shown when there are todos -->
            <section class="main">
                <input id="toggle-all" class="toggle-all" type="checkbox" :checked="checkedAll" @change="selectAll">
                <label for="toggle-all"  >Mark all as complete</label>
                <ul class="todo-list">
                    <!-- These are here just to show the structure of the list items -->
                    <!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
                    <li :class="{completed:item.isComplete, editing:item == editedTodo}" v-for="(item,index) in filterData">
                        <div class="view">
                            <input class="toggle" type="checkbox" :checked="item.isComplete" @change="change(item)">
                            <label @dblclick="edit(item)">{{item.title}}</label>
                            <button class="destroy" @click="deleteTodo(index)"></button>
                        </div>
                        <input class="edit" v-model="item.title" 
                            @blur="doneEdit(item)"
                            @keyup.enter="doneEdit(item)"
                            @keyup.esc="cancelEdit(item)"
                        >
                    </li>
                </ul>
            </section>
            <!-- This footer should hidden by default and shown when there are todos -->
            <footer class="footer">
                <!-- This should be `0 items left` by default -->
                <span class="todo-count"><strong>{{unComplete}}</strong> item left</span>
                <!-- Remove this if you don't implement routing -->
                <ul class="filters">
                    <li>
                        <a :class="{selected:visibility=='all'}" href="#/" @click.prevent="showPanel('all')">All</a>
                    </li>
                    <li>
                        <a :class="{selected:visibility=='active'}" href="#/active" @click.prevent="showPanel('active')">Active</a>
                    </li>
                    <li>
                        <a :class="{selected:visibility=='completed'}" href="#/completed" @click="showPanel('completed')">Completed</a>
                    </li>
                </ul>
                <!-- Hidden if no completed items are left ↓ -->
                <button class="clear-completed" @click="ClearCompleted">Clear completed</button>
            </footer>
        </section>
        <footer class="info">
            <p>Double-click to edit a todo</p>
            <!-- Remove the below line ↓ -->
            <p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
            <!-- Change this out with your name and url ↓ -->
            <p>Created by <a href="http://todomvc.com">you</a></p>
            <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
        </footer>
        <!-- Scripts here. Don't remove ↓ -->
        <script src="node_modules/todomvc-common/base.js"></script>
        <script src="js/app.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
        <script>
             new Vue({
                 el:".todoapp",
                 data(){
                     return {
                         todoList:[
                             {title:"xx",isComplete:false},
                             {title:"xx",isComplete:false}
                         ],
                         needTodoText:'',
                         visibility:"all", // 通过这个属性值的变化对数据进行筛选
                         checkedAll:false,  // 全选按钮
                         editedTodo:null
                     }
                 },
                 methods: {
                     // 添加todoList
                    addTodo(){
                        if(this.needTodoText){
                            this.todoList.push({
                                    title:this.needTodoText,
                                    isComplete:false
                            });
                            this.needTodoText = "";
                        }
                    },
                    // 删除
                    deleteTodo(index){
                        this.todoList.splice(index,1)
                    },
                    showPanel(s){
                        this.visibility = s
                    },
                    change(item){
                        item.isComplete = !item.isComplete
                    },
                    ClearCompleted(){
                        this.todoList = this.todoList.filter(item=>{
                            return !item.isComplete
                        })
                    },
                    selectAll(){
                        if(this.checkedAll){
                             this.todoList.forEach(item=>{
                                  item.isComplete = false
                             })
                        }else{
                            this.todoList.forEach(item=>{
                                item.isComplete = true
                            })
                        }
                        this.checkedAll = !this.checkedAll
                    },
                    edit(todo){
                         console.log(todo)
                         this.editedTodo = todo
                         this.beforeEditCache = todo.title
                    },
                    doneEdit(todo){
                        if (!this.editedTodo) {
                            return
                        }
                        this.editedTodo = null
                        todo.title = todo.title.trim()
                        if (!todo.title) {
                            this.deleteTodo(todo)
                        }
                    },
                    cancelEdit(todo){
                        this.editedTodo = null
                  todo.title = this.beforeEditCache
                    }
                 },
                 watch: {
                        todoList: {
                            handler: function (todos) {
                                 var countAll = todos.length;
                                 var countComplete = 0
                                 todos.forEach(item=>{
                                      if(item.isComplete){
                                            countComplete++
                                        }
                                 })
                                 if(countComplete === countAll){
                                      this.checkedAll = true
                                 }else{
                                     this.checkedAll = false
                                 }
                            },
                            deep: true
                        }
                 },    
                 computed: {
                    unComplete(){
                        return this.todoList.filter(item=>{
                                return !item.isComplete
                        }).length
                    },
                    filterData(){
                        //过滤的时候有三种情况 all completed unCompleted
                        var filter = {
                                all:function(list){
                                        return list;
                                },
                                completed:function(list){
                                        return list.filter(item=>{
                                                return item.isComplete;
                                        })
                                },
                                active:function(list){
                                        return list.filter(item=>{
                                                return !item.isComplete;
                                        })
                                }
                        }
                        //如果找到了过滤函数,就返回过滤后的数据,如果没有找到就返回所有的数据
                        return filter[this.visibility]?filter[this.visibility](this.todoList):this.todoList;
                    },
                },
             })
        </script>
    </body>
</html>

第四章Vue-Cli脚手架

Vue CLI 是 Vue 的脚手架工具,它可以帮助我们快速生成 Vue 基础项目代码,提供开箱即用的功能特性。

  • 基础代码目录结构
  • 开发服务
  • 本地调试
  • 代码部署
  • 热加载
  • 单元测试 ....

Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性

安装

依赖要求:Vue CLI 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+)。

npm install -g @vue/cli

使用 vue --version 确认是否安装成功。
cli-01.png

创建项目

运行以下命令来创建一个新项目

vue create my-project
微信截图_20190710144811.png

选择默认按enter即可

启动开发模式:npm run serve

开发期间不要关闭命令窗口,如果关了,就重新 npm run serve

浏览器中打开http://localhost:8080/看到vue初始页面,则说明初始化创建成功了

目录结构

cli-02.png
名称 说明
node_modules 第三方包存储目录
public 静态资源,已被托管
src 源代码
.gitignore git 忽略文件,暂时不关心,我们还没有在项目中使用 git
babel.config.js 先不关心
package.json 包说明文件
README.md 项目说明文件
package-lock.json 包的版本锁定文件

src 目录结构

src.png
名称 说明
assets 资源目录,存储静态资源,例如图片等
components 存储其它组件的目录
App.vue 根组件
main.js 入口文件

程序的启动

  • 找到 main.js 入口
  • 加载 Vue
  • 加载 App 组件
  • 创建 Vue 实例
  • 将 App 组件替换到入口节点

.vue 单文件组件

template

  • 作用:组件的模板

  • 注意:只能有一个根节点(template 本身不算)

    <template>
    <div id="app">
     <img alt="Vue logo" src="./assets/logo.png">
    </div>
    </template>
    

script

  • 作用:组件的 JavaScript ,用来配置组件的选项(data、methods、watch。。。)
<script>
  // 1. 使用一个普通对象配置组件的选项
  const componentOptions = {
    data() {
      return {}
    },
    methods: {}
  }
  // 2. 将这个对象导出(组件的选项对象必须显式的导出,否则不会生效)
  export default componentOptions
</script>

为了方便,我们可以直接在定义的同时直接导出

<script>
  export default {
    data() {
      return {}
    },
    methods: {}
  }
</script>

style

当 <style> 标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素。

<style scoped>
  .example {
    color: red;
  }
</style>

<template>
  <div class="example">hi</div>
</template>

单文件组件的定义和使用

创建单文件组件

  • 推荐把通用组件创建到 components 目录中
  • 把视图组件定义到 views 目录中
  • 单文件组件只是承载组件的容器而已,既不是全局也不是局部,如果要使用这个单文件组件,必须 注册
    • 全局注册使用,可以在任何组件中使用
      • 局部注册使用,只能在注册的组件中被使用

建立一个单文件的组件

<template>
  <div>
    foo 组件
  </div>
</template>

<script>
  export default {
    data() {},
    methods: {}
    // ...
  }
</script>

<style></style>

全局注册使用

在 main.js 文件中

...
import Vue from 'vue'
import Com1 from './components/Com1.vue'

...

Vue.component('Com1', Com1)
// 接下来就可以在任何组件中使用 Com1 组件了

局部注册使用

在某个组价中局部注册使用

<template>
  <div>
    <!-- 使用 Com2 组件 -->
    <Com2></Com2>
  </div>
</template>
<script>
  import Com2 from './components/Com2'

  export default {
    components: {
      Com2
    }
  }
</script>

ECMAScript 6 Module

历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD(require.js 库,专门用于在浏览器中进行模块化开发,几乎已经淘汰了) 两种。前者用于服务器(Node.js),后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

学习准备环境

目前无论是 Node.js 还是浏览器都还无法直接原生支持 ECMAScript 6 模块 API(import、export)。

但是对于浏览器来说我们可以使用构建工具将模块代码转换为浏览器能识别的代码。

Node.js 中可以开启实验功能来支持该功能。

为了学习方便,我们这里使用 Node 环境来学习 ES6 模块规则。

首先将你的文件后缀名定义为 .mjs,然后执行这个脚本文件的时候加上 --experimental-modules 选项。

例如我有一个 main.mjs,则执行命令是:node --experimental-modules main.js

这里只是用于测试学习,不要在生产环境中使用这个模块规则。

foo.js

// export 也用于导出
// 但是可以多次使用
export const a = 1
export const b = 2
export const c = 3
export const d = 4

function add(x, y) {
  return x + y
}

// 等价于 module.exports = add
// export default 只能使用一次
export default add

// 模块中有多个成员,一般都使用 export xxx
// 如果只有一个成员,那就 export default

main.js

// 加载 export default 导出的成员
// import foo from './foo'
// console.log(foo)

// 按需加载 export 导出的成员
// import { a, b } from './foo.mjs'
// console.log(a, b)

// 一次性加载所有成员(包括 default 成员)
// import * as foo from './foo.mjs'
// console.log(foo)
// console.log(foo.a)
// console.log(foo.default(10, 3)) // 很少这样去访问 default 成员

// 为了方便,先加载默认 default 成员,然后加载 其它 export 成员
// import abc, { a, b } from './foo'
// console.log(abc) // export default 成员
// console.log(a, b) //

// 可以使用 as 起别名
import { a as aa } from './foo'
console.log(aa)

// console.log(foo(1, 2))

相关命令

# 启动开发服务
npm run serve
# 项目打包
npm run build
# 代码检查
npm run lint

第五章vue中和服务端通信

概述

Vue 不像 jQuery 内置了 ajax 请求函数,在 Vue 中没有提供这样的功能。所以当我们需要在 Vue 中和服务端进行通信的时候可选择的方式会更灵活一些。

注意:Vue 不提供的原因是为了让 Vue 本身更专注于视图部分,保持其渐进灵活的特性

所以 Vue 给了我们更多的选择空间,例如我们可以使用下面的可选方案:

  • 原生的 XMLHttpRequest
  • 原生的 Fetch
  • 也可以结合使用 jQuery 自带的 Ajax 请求函数
  • 早期大家开发 Vue 应用喜欢使用一个第三方插件:Vue Resource
  • 目前主流的方案是使用社区中知名的第三方库 axios

axios介绍

axios 是一个基于 Promise 的第三方 HTTP 客户端请求库,可以用于浏览器或者 Node.js。 axios 本身和 Vue 没有一毛钱关系,只是简单纯粹的封装了 HTTP 请求功能。可以运行在任何支持 JavaScript 环境的平台。

axios 依赖原生的 ECMAScript 6 Promise 支持。如果浏览器不支持 ECMAScript 6 Promise,可以使用 es6-promise 进行兼容处理

起步

npm install axios

执行一个 GET 请求

const axios = require('axios');

// Make a request for a user with a given ID
axios.get('/user?ID=12345')
  .then(function (response) {
    // handle success
    console.log(response);
  })
  .catch(function (error) {
    // handle error
    console.log(error);
  })
  .finally(function () {
    // always executed
  });

// Want to use async/await? Add the `async` keyword to your outer function/method.
async function getUser() {
  try {
    const response = await axios.get('/user?ID=12345');
    console.log(response);
  } catch (error) {
    console.error(error);
  }
}

执行一个 POST 请求

axios
  .post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function(response) {
    console.log(response)
  })
  .catch(function(error) {
    console.log(error)
  })

执行多个并发请求

function getUserAccount() {
  return axios.get('/user/12345')
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions')
}

axios.all([getUserAccount(), getUserPermissions()]).then(
  axios.spread(function(acct, perms) {
    // Both requests are now complete
  })
)

axios API

axios(config),我们可以像使用 $.ajax() 一样来使用 axios

// Send a POST request
axios({
  method: 'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
})
// GET request for remote image
axios({
  method: 'get',
  url: 'http://bit.ly/2mTM3nY',
  responseType: 'stream'
}).then(function(response) {
  response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
})

第六章组件基础

  • 了解组件的概念
  • 掌握组件的定义
  • 了解组件的组织方式
  • 掌握组件的声明周期
  • 掌握组件通信

组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。
组件化.png

.vue 单文件组件

单文件组件是最常用的编写组件的方式,我们90%以上都是这种写法,还有其他写法很少用,暂时不用掌握

组件注册方式

  • 全局组件
    • 定义在全局,在任意组件中都可以直接使用
  • 局部组件
    • 定义在组件内部,只能在当前组件使用

注册和使用方式清跳转到 第四章的Vue-cli的 单文件组件的定义和使用 这一章节

组件的生命周期

组件的有几个阶段

  • 初始化阶段
  • 运行中阶段
  • 销毁阶段

初始化阶段

  • beforeCreate
表示组件创建前的准备工作, 为事件的发布订阅 和 生命周期的开始做初始化
这个钩子函数中,数据拿不到, 真实DOM也拿不到,这个钩子在项目中我们没有什么实际用途
 beforeCreate () { //表示组件创建前的准备工作( 初始化事件和生命周期 )
        console.log('beforeCreate') 
        console.log(this.msg) //undefind
        console.log(document.querySelector('p')) //null 没有真实DOM
}
  • created
表示组件创建结束,这个钩子函数中,数据拿到了, 但是真实DOM没有拿到
这个钩子函数在项目一般数据请求,然后可以进行一次默认数据的修改
created () { // 组件创建结束
        console.log('created')
        console.log(this.msg) //(vue.js) 有数据
        console.log(document.querySelector('p')) //null 没有真实DOM
        axios({
            url: './data.json'
        })
        .then( res => {
                this.msg = res
        })
        .catch( error => {
                throw error
        })
}
  • beforeMounte
表示组件装载前的准备工作,判断 el选项有没有, 判断 template选项有没有 , 如果没有, 那么需要手动装载,如果有,那么通过render函数进行模板的渲染
这个钩子函数中,数据拿到了, 真实DOM没有拿到,这个钩子函数在项目中,数据请求, 它也可以进行一次数据修
beforeMount () {
        console.log('beforeMount')
        console.log(this.msg) //(vue.js) 有数据
        console.log(document.querySelector('p')) //null 没有真实DOM
        // axios({
        //     url: './data.json'
        // })
        //   .then( res => {
        //       this.msg = res
        //   })
        //   .catch( error => {
        //       throw error
        //   })
  }
  • mounted
表示组件装载结束, 就是我们可以在视图中看到了,这个钩子函数中,数据拿到了, 真实DOM也拿到了,
这个钩子函数在项目DOM操作就可以进行了, 第三方库的实例化
mounted () {
        console.log('mount')
        console.log(this.msg) //(vue.js) 有数据
        console.log(document.querySelector('p')) //有真实DOM
        axios({
                url: './data.json'
        })
        .then( res => {
                this.msg = res
        })
        .catch( error => {
                throw error
        })
}

总结: 由上对比,我们可以知道, 数据请求越提前越好一些, created常用于数据的请求和数据的修改, 第三方库的实例化常在mounted中进行书写

运行中阶段

  • beforeUpdate
表示数据更新前的准备工作,这个钩子不主动执行,当数据修改了, 才会执行,这个钩子函数中数据拿到了, 并且拿到的是修改后的数据,DOM也输出了。这个钩子函数更多的工作内容为:生成新的 VDOM , 然后通过diff算法进行两次VDOM 对比。这个钩子在项目中。因为他主要做的事情是内部进行的, 所以对我们而言没有太多的操作意义
  • updated
表示数据更新结束, 通过render函数渲染真实DOM。这个钩子函数的执行也是, 当数据修改的时候才执行。这个钩子函数中数据拿到了, DOM也拿到了。

总结: 数据更新, 也要进行DOM操作那么, 我们使用update这个钩子

销毁阶段

  • beforeDestroy
  • destroyed
这两个钩子无差别,在项目中做善后工作 , 手动清除一些计时器, 和一些方法, 还有第三方实例化出来的对象
Vue.component('LifeCircle',{
    template: '#life-circle',
    methods: {
        destroy(){
            this.$destroy()
        }
    },
    created () {
        this.timer = setInterval( () => {
            console.log('1')
        },1000)
    },
    beforeDestroy () {
        console.log('beforeDestory')
    },
    destroyed () {
        console.log('destroyed')
        clearInterval( this.timer )
        // 如果是用$destroy这个方法来清除组件, 那么我们必须手动清除这个组件的外壳
        document.querySelector('#app div').remove()
    }
})
new Vue({
    el: '#app',
    data: {
        flag: true
    }
})

组件通信

组件就像零散的积木,我们需要把这些积木按照一定的规则拼装起来,而且要让它们互相之间能进行通讯,这样才能构成一个有机的完整系统。

在真实的应用中,组件最终会构成树形结构,就像人类社会中的家族树一样

在树形结构里面,组件之间有几种典型的关系:父子关系、兄弟关系、没有直接关系。

相应地,组件之间有以下几种典型的通讯方案:

直接的父子关系

  • 父组件通过 this.$refs 访问子组件
  • 子组件 this.$parent 访问其父组件

直接父子关系

  • 父组件通过 Props 给子组件下发数据
  • 子组件通过事件方式给父组件发送消息

没有直接关系

  • 简单场景:借助于事件机制进行通讯
  • 复杂场景:使用状态管理容器(例如 Vue 生态中的 Vuex、React 生态中的 Redux、Mobx 等)

利用 sessionStorage 和 localStorage 进行通讯

无论你使用什么前端框架,组件之间的通讯都离开不以上几种方案,这些方案与具体框架无关。

父子组件通信

组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。然而,通过一个良好定义的接口来尽可能将父子组件解耦也是很重要的。这保证了每个组件的代码可以在相对隔离的环境中书写和理解,从而提高了其可维护性和复用性。 在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。看看它们是怎么工作的。

父传子(Props Down)

我们可以用 v-bind 来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件:

父组件
<div>
  <input v-model="parentMsg">
  <br>
  <child :myMessage="parentMsg"></child>
    <!-- <child v-bind:my-message="parentMsg"></child> -->
</div>

子组件通过prop属性接受,prop中的变量名不能在data中定义

<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
  name: 'app',
    props:['myMessage']
  components: {
    HelloWorld
  }
}
</script>

单向数据流

Prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解。

另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。

另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。

  • 1、Prop 作为初始值传入后,子组件想把它当作局部数据来用
  • 2、Prop 作为原始数据传入,由子组件处理成其它数据输出

对这两种情况,正确的应对方式是:

  1. 定义一个局部变量,并用 prop 的值初始化它:

    props: ['initialCounter'],
    data: function () {
    // var a = 1
    // var b = a
    // a = 123
    // b ?
    // b 456
    // a ?
    return { counter: this.initialCounter }
    }
    

    2.定义一个计算属性,处理 prop 的值并返回:

    // ...
    props: ['size'],
    computed: {
    normalizedSize: function () {
     return this.size.trim().toLowerCase()
    }
    },
    

    注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。即便引用类型可以,也不要利用这个特性,记住一个原则:组件的数据状态在组件内部管理维护,不要在其他位置去修改它

Prop 验证(这个我们当前用不到,有多余时间了解的可以学,不讲)

我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用。 要指定验证规则,需要用对象的形式来定义 prop,而不能用字符串数组:

export default {
    data(){
        return {}
    },
  props: {
    // 基础类型检测 (`null` 指允许任何类型)
    propA: Number,
    // 可能是多种类型
    propB: [String, Number],
    // 必传且是字符串
    propC: {
      type: String,
      required: true
    },
    // 数值且有默认值
    propD: {
      type: Number,
      default: 100
    },
    // 数组/对象的默认值应当由一个工厂函数返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
}

type 可以是下面原生构造器:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol type 也可以是一个自定义构造器函数,使用 instanceof 检测。当 prop 验证失败,Vue 会抛出警告 (如果使用的是开发版本)。 注意 prop 会在组件实例创建之前进行校验,所以在 default 或 validator 函数里,诸如 data、computed 或 methods 等实例属性还无法使用。

子传父(Events Up)

我们知道,父组件使用 prop 传递数据给子组件。但子组件怎么跟父组件通信呢?这个时候 Vue 的自定义事件系统就派得上用场了。

1、在子组件中调用 $emit() 方法发布一个事件

methods: {
    incrementCounter: function () {
        this.counter += 1
        // 发布一个名字叫 increment 的事件
        this.$emit('increment')
        // this.$emit('update:foo', "传递过去的参数");
    }
},

2、在父组件中提供一个子组件内部发布的事件处理函数

new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
});
  1. 在使用子组件的模板的标签上订阅子组件内部发布的事件

    <div id="counter-event-example">
    <p>{{ total }}</p>
    <!--
     订阅子组件内部发布的 increment 事件
     当子组件内部 $commit('increment') 发布的时候,就会调用到父组件中的 incrementTotal 方法
    -->
    <button-counter v-on:increment="incrementTotal"></button-counter>
    </div>
    

非父子组件通信

简单场景:Event Bus (少用)

有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:

var bus = new Vue();
// 触发组件 A 中的事件
bus.$emit('id-selected', 1);

// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
  // ...
});

复杂场景:Vuex(最常用)

在复杂的情况下,我们应该考虑使用专门的状态管理模式

第七章Vuex

组件通信

  • 父子通信
    • Props Down
    • Events Up
  • 非父子怎么办?
    • Vuex
    • 全局的 Global Bus

Vuex 是什么

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能
vuex.png

什么情况下我应该使用 Vuex

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。

如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

基本用法

在SPA单页面组件的开发中 Vue的vuex和React的Redux 都统称为同一状态管理,个人的理解是全局状态管理更合适;简单的理解就是你在state中定义了一个数据之后,你可以在所在项目中的任何一个组件里进行获取、进行修改,并且你的修改可以得到全局的响应变更。下面咱们一步一步地剖析下vuex的使用:

1、安装vuex

npm install vuex --save

2、创建仓库 然后 在src文件目录下新建一个名为store的文件夹,为方便引入并在store文件夹里新建一个index.js,里面的内容如下: 如果脚手架生成有vuex那直接跳到3步。

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store();

export default store;

接下来,在 main.js里面引入store,然后再全局注入一下,这样一来就可以在任何一个组件里面使用this.$store了:

import store from './store'//引入store
new Vue({
    store
  render: h => h(App),
}).$mount('#app')

state:状态

设置仓库要保存的值和操作那些值的方法 回到store文件的index.js里面,我们先声明一个state变量,并赋值一个空对象给它,里面随便定义两个初始属性值;然后再在实例化的Vuex.Store里面传入一个空对象,并把刚声明的变量state仍里面:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
 const state={//要设置的全局访问的state对象
     showFooter: true,
     changableNum:0
     //要设置的初始属性值
   };
 const store = new Vuex.Store({
       state
    });
export default store;

getters:计算属性

现在可以用this.store.state.showFooter或this.store.state.changebleNum在任何一个组件里面获取showfooter和changebleNum定义的值了,但这不是理想的获取方式;vuex官方API提供了一个getters,和vue计算属性computed一样,来实时监听state值的变化(最新状态),并把它也仍进Vuex.Store里面,具体看下面代码:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
 const state={   //要设置的全局访问的state对象
     showFooter: true,
     changableNum:0
     //要设置的初始属性值
   };
const getters = {   //实时监听state值的变化(最新状态)
    isShow(state) {  //方法名随意,主要是来承载变化的showFooter的值
       return state.showFooter
    },
    getChangedNum(){  //方法名随意,主要是用来承载变化的changableNum的值
       return state.changebleNum
    }
};
const store = new Vuex.Store({
       state,
       getters
});
export default store;

mutations:更改状态

光有定义的state的初始值,不改变它不是我们想要的需求,接下来要说的就是mutations了,mutattions也是一个对象,这个对象里面可以放改变state的初始值的方法,具体的用法就是给里面的方法传入参数state或额外的参数,然后利用vue的双向数据驱动进行值的改变,同样的定义好之后也把这个mutations扔进Vuex.Store里面,如下:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
 const state={   //要设置的全局访问的state对象
     showFooter: true,
     changableNum:0
     //要设置的初始属性值
   };
const getters = {   //实时监听state值的变化(最新状态)
    isShow(state) {  //承载变化的showFooter的值
       return state.showFooter
    },
    getChangedNum(){  //承载变化的changebleNum的值
       return state.changableNum
    }
};
const mutations = {
    show(state) {   //自定义改变state初始值的方法,这里面的参数除了state之外还可以再传额外的参数(变量或对象);
        state.showFooter = true;
    },
    hide(state) {  //同上
        state.showFooter = false;
    },
    newNum(state,sum){ //同上,这里面的参数除了state之外还传了需要增加的值sum
       state.changableNum+=sum;
    }
};
 const store = new Vuex.Store({
       state,
       getters,
       mutations
});
export default store;

actions:异步操作

这时候你完全可以用 this.store.commit('show') 或 this.store.commit('hide') 以及 this.store.commit('newNum',6) 在别的组件里面进行改变showfooter和changebleNum的值了,但这不是理想的改变值的方式;因为在 Vuex 中,mutations里面的方法 都是同步事务,意思就是说:比如这里的一个this.store.commit('newNum',sum)方法,两个组件里用执行得到的值,每次都是一样的,这样肯定不是理想的需求

vuex官方API还提供了一个actions,这个actions也是个对象变量,最大的作用就是里面的Action方法 可以包含任意异步操作,这里面的方法是用来异步触发mutations里面的方法,actions里面自定义的函数接收一个context参数和要变化的形参,context与store实例具有相同的方法和属性,所以它可以执行context.commit(' '),然后也不要忘了把它也扔进Vuex.Store里面:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
 const state={   //要设置的全局访问的state对象
     showFooter: true,
     changableNum:0
     //要设置的初始属性值
   };
const getters = {   //实时监听state值的变化(最新状态)
    isShow(state) {  //承载变化的showFooter的值
       return state.showFooter
    },
    getChangedNum(){  //承载变化的changebleNum的值
       return state.changableNum
    }
};
const mutations = {
    show(state) {   //自定义改变state初始值的方法,这里面的参数除了state之外还可以再传额外的参数(变量或对象);
        state.showFooter = true;
    },
    hide(state) {  //同上
        state.showFooter = false;
    },
    newNum(state,sum){ //同上,这里面的参数除了state之外还传了需要增加的值sum
       state.changableNum+=sum;
    }
};
 const actions = {
    hideFooter(context) {  //自定义触发mutations里函数的方法,context与store 实例具有相同方法和属性
        context.commit('hide');
    },
    showFooter(context) {  //同上注释
        context.commit('show');
    },
    getNewNum(context,num){   //同上注释,num为要变化的形参
        context.commit('newNum',num)
     }
};
  const store = new Vuex.Store({
       state,
       getters,
       mutations,
       actions
});
export default store;

this.store.commit('increment') 触发mutations this.store.store.dispatch('increment') 触发 actions

// 以载荷形式分发 actions
this.$store.dispatch('incrementAsync', {
  amount: 10
})
// 以对象形式分发 actions
this.$store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

而在外部组件里进行全局执行actions里面方法的时候,你只需要用执行this.$store.dispatch('hideFooter')this.$store.dispatch('showFooter') 以及this.$store.dispatch('getNewNum',6)//6要变化的实参,这样就可以全局改变改变showfooter或changebleNum的值了

不要滥用 Vuex,推荐方式一,能父子的就父子,非父子的使用 Vuex。

总结:

  • 把需要共享的状态放到 Vuex 容器中进行管理,不需要共享的还是放到组件内部去管理
  • 容器也支持计算属性 getters
  • 修改容器的状态务必通过 mutation 函数
    • 注意:不要在 mutation 函数中执行异步操作去修改 state
  • 如果需要需要执行异步操作修改 state
    • 定义 action
    • 在 action 中执行异步操作
    • 在 action 中执行异步操作结束只有通过提交 mutation 的方式来修改数据状态

简单一句话:非异步操作修改状态使用 mutation,异步操作修改状态使用 action 。其次我们使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

modules 模块化 以及 组件中引入 mapGetters、mapActions 和 mapStates的使用,这些目前不讲,有精力的同学可以自行看

第八章 Vue-router

单页面应用程序:

  • 网易云音乐
  • CODING

单页应用(英语:single-page application,缩写SPA):在传统的网页应用中,浏览器更多的是充当一个展示层,路由处理、服务调用、页面跳转流程都由服务端来处理。即 MVC 都放在服务器端,SPA技术将逻辑从服务器转移到了客户端。这导致Web服务器发展为一个纯数据API或Web服务。这种架构的转变在一些圈子中被称为“瘦服务器架构”,以强调复杂性已从服务端转移到客户端,并认为这最终降低了系统的整体复杂性。

传统的网站有以下特点:

  • 重服务端,由于 MVC 都存在于服务器上,因此这类应用在开发资源和开发的重心都偏向后端,往往是后端工程师来主导整个项目开发;
  • 页面频繁刷新,由于浏览器端只是一个展现层,当页面功能有所变化的时,页面就刷新,这会导致资源的浪费,用户需要花费额外的时间等待页面刷新,用户体验不佳。

单页面应用,只有一张Web页面的应用,是一种从Web服务器加载的富客户端,单页面跳转仅刷新局部资源 ,公共资源(js、css等)仅需加载一次。

SPA优缺点

优点:

  • 无刷新体验,这个应该是最显著的有点,由于路由分发直接在浏览器端完成,页面是不刷新,对用户的响应非常及时,因此提升了用户体验
  • 减轻服务器压力,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍
  • 完全的前端组件化,前端开发不再以页面为单位,更多地采用组件化的思想,代码结构和组织方式更加规范化,便于修改和调整
  • 良好的前后端分离开发,前端负责界面显示,后端负责数据存储和计算,各司其职,不会把前后端的逻辑混杂在一起

缺点:

  • 不利于 SEO,单页页面,数据在前端渲染,就意味着没有 SEO,或者需要使用变通的方案(不过目前可以配置服务端渲染来解决这个问题,但还不够足够成熟)
  • 初次加载耗时相对增多,要在一个页面上为用户提供产品的所有功能,在这个页面加载的时候,首先要加载大量的静态资源,这个加载时间相对比较长
  • 较高的前端开发门槛,对开发人员技能水平要求较高,不再是『切切图,画画页面这么简单』
  • 低版本浏览器兼容差

路由

前端路由目前主要有两种方法:

  • 利用url的hash,就是常用的锚点(#)操作,类似页面中点击某小图标,返回页面顶部,JS通过hashChange事件来监听url的改变,IE7及以下需要轮询进行实现。一般常用框架的路由机制都是用的这种方法,例如Angualrjs自带的ngRoute和二次开发模块ui-router,react的react-route,vue-route…
  • 利用HTML5的History模式,使url看起来类似普通网站,以”/”分割,没有”#”,但页面并没有跳转,不过使用这种模式需要服务器端的支持,服务器在接收到所有的请求后,都指向同一个html文件,通过historyAPI,监听popState事件,用pushState和replaceState来实现。

SPA 前端路由原理与实现方式:通常 SPA 中前端路由有2中实现方式

  • 修改 url 中 Hash
  • 利用 H5 中的 history

Vue-router

Vue-router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌

安装

使用脚手架初始化的时候可以初始化安装vue-router,如果没有就手动下载和使用

npm install vue-router

使用

新建router文件夹,然后建立一个index.js文件,写入路由的代码

import Vue from 'vue'   //引入Vue
import Router from 'vue-router'  //引入vue-router
import Hello from '@/components/HelloWorld'  //引入组件目录下的HelloWorld.vue组件
import Demo from '@/components/Demo'  //引入组件目录下的Demo.vue组件
Vue.use(Router)  //Vue全局使用Router
export default new Router({
  routes: [              //配置路由,这里是个数组
    {                    //每一个链接都是一个对象
      path: '/',         //链接路径 当路径为 http://localhost:8080/#/显示此组件
      name: 'Hello',     //路由名称,
      component: Hello   //对应要显示的组件
        },
        {                    //每一个链接都是一个对象
      path: '/demo',     //链接路径,当路径为 http://localhost:8080/#/demo显示此组件
      name: 'Demo',     //路由名称,
      component: Demo   //对应要显示的组件
    }
  ]
})

在main.js中使用router

import Vue from 'vue'
import App from './App.vue'
import router from "./router/index.js" // 导入路由
Vue.config.productionTip = false

new Vue({
    router, // 使用路由
  render: h => h(App),
}).$mount('#app')

在App.js中要使用 router-view组件

<template>
  <div id="app">
        <img alt="Vue logo" src="./assets/logo.png">
        <!-- 路由出口 -->
      <!-- 路由匹配到的组件将渲染在这里 -->
        <router-view></router-view> 
  </div>
</template>

路由跳转方式

  • 声明式导航

    <template>
    <div id="app">
    <h1>Hello App!</h1>
    <p>
     <!-- 使用 router-link 组件来导航. -->
     <!-- 通过传入 `to` 属性指定链接. -->
     <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
     <router-link to="/">Go to root</router-link>
         <router-link to="/demo">Go to demo</router-link>
    </p>
    <!-- 路由出口 -->
    <!-- 路由匹配到的组件将渲染在这里 -->
    <router-view></router-view>
    </div>
    </template>
    
  • 编程式导航

除了使用 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现

前进和后退this.router.go(-1) 和 this.router.go(1):功能跟我们浏览器上的后退和前进按钮一样,这在业务逻辑中经常用到。比如条件不满足时,我们需要后退。

app.vue文件里加入一个按钮,按钮并绑定一个goback( )方法


<button @click="goback">后退</button>
<script>
export default {
  name: 'app',
  methods:{
    goback(){
      this.$router.go(-1);
    }
  }
}
</script>

编程式导航都作用就是跳转,比如我们判断用户名和密码正确时,需要跳转到用户中心页面或者首页,都用到这个编程的方法来操作路由。this.$router.push

export default {
  name: 'app',
  methods:{
    goback(){
      this.$router.go(-1);
    },
    goHome(){
            // 字符串
      this.$router.push('/');
    }
  }
}

子路由

export default new Router({
  routes: [              //配置路由,这里是个数组
    {                    //每一个链接都是一个对象
      path: '/',         //链接路径
      name: 'Hello',     //路由名称,
      component: Hello   //对应要显示的组件
        },
        {                    //每一个链接都是一个对象
      path: '/demo',     //链接路径
      name: 'Demo',     //路由名称,
      component: Demo   //对应要显示的组件
        },
        {
            // 对某个路由使用子路由,那么需得在Hi1组件内使用 <router-view></router-view>,否则切换
            // 了子路由也无法显示出来
            path:'/h1',
            name:'Hi1',
            component:Hi1,  // 当访问 /h1的时候显示Hi1这个组件
            children:[
                {path:"/", component:Hi1}, // 当访问 /h1的时候<router-view></router-view>被这个组件替换,就会有2个Hi1组件内容。一般不这么写,一般这么写{ path: '', component: Hi1 },
                {path:"h2", component:Hi2}, // 当 /h1/h2 匹配成功,Hi2会被渲染在 Hi1 的 <router-view> 中
                {path:"h3", component:Hi3}  // 当 /h1/h3 匹配成功,Hi3会被渲染在 Hi1 的 <router-view> 中
            ]
        }
  ]
})

动态路由

  • 动态路由的匹配和获取值
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})
---------在User组件中可以通过下面方式去拿------------
{{$route.params.id}}
  • 动态路由的跳转
第一种声明式动态路由跳转
<!-- 动态路由通过命名路由跳转  /user/123   -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
第二种编程式导航的动态路由跳转
const userId = '123'
// 对象,命名的动态路由
router.push({ name: 'user', params: { userId }}) // -> /user/123

// 如果提供了 path,params 会被忽略
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

路由重定向

重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})

重定向的目标也可以是一个命名的路由:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

路由守卫

(路由的声明周期钩子函数,进阶部分学习)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Vue 3.0 性能提升主要是通过哪几方面体现的? vue2在初始化的时候,对data中的每个属性使用define...
    Smallbore阅读 1,157评论 0 8
  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,202评论 0 6
  • 要招一个会vue的开发者: 作为面试官的你,你还会每次都只是问这些老土的问题吗?你对MVVM的理解是什么?你知道什...
    浪子神剑阅读 23,028评论 0 259
  • Vue也已经升级到2.0版本了,到现在为止(2016/11/19)比较流行的MVVM框架有AngularJS(也有...
    彬_仔阅读 27,206评论 12 114
  • vue-cli搭建项目 确保安装了node与npm 再目标文件夹下打开终端 执行cnpm i vue-cli -g...
    Akiko_秋子阅读 3,214评论 1 22