欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Vue.js学习

程序员文章站 2024-03-23 08:01:21
...

Vue.js 是用于构建交互式的 Web 界面的库。
Vue.js 提供了 MVVM 数据绑定和一个可组合的组件系统,具有简单、灵活的 API。

其实和Jquery一样,VueJs就是一个Js库,但是是面向前端的库,具体来讲叫做MVVM(Model-View-ViewModel)库.
也就是说,有部分功能和Jquery是差不多的,Vuejs能做的,Jquery也能做。这下我们就放心了,Jquery多简单啊,令人发指的是Vuejs在实现相同功能的时候更简单(不然用你干嘛啊);

理解Vuejs最关键的一句话叫做“数据驱动视图”,比如用Jquery来做一个列表,这个列表的数据是从Laravel来的,那么我们要遍历这个数据,然后把列表的html元素加到dom里面去, 要删除一个列表项的时候,先要在找到列表项在dom的位置,然后去除这个节点。Vuejs不用,数据在的时候,列表就在,数据减一,列表项就自动实时相应减一。也就是说,你只要操作数据就够了,不用管dom。这基本就是Vuejs的中心思想。

一、基础学习

1.MVVM模式

下图不仅概括了MVVM模式(Model-View-ViewModel),还描述了在Vue.js中ViewModel是如何和View以及Model进行交互的。

Vue.js学习

ViewModel是Vue.js的核心,它是一个Vue实例。Vue实例是作用于某一个HTML元素上的,这个元素可以是HTML的body元素,也可以是指定了id的某个元素。

当创建了ViewModel后,双向绑定是如何达成的呢?

首先,我们将上图中的DOM Listeners和Data Bindings看作两个工具,它们是实现双向绑定的关键。
从View侧看,ViewModel中的DOM Listeners工具会帮我们监测页面上DOM元素的变化,如果有变化,则更改Model中的数据;
从Model侧看,当我们更新Model中的数据时,Data Bindings工具会帮我们更新页面中的DOM元素。

1.2 Vue基本语法

先来看一个简单的示例,Hello,World!

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue学习</title>
<script src="http://static.runoob.com/assets/vue/1.0.11/vue.min.js"></script>
</head>
<body>
<div id="app">
  {{ message }}
</div>
<!-- JavaScript 代码需要放在尾部(指定的HTML元素之后) -->
<script>
new Vue({
    el:'#app',
    data: {
        message:'Hello World!'
    }
});
</script>
</body>
</html>

示例详解:

1.2.1 data属性和方法

每个Vue实例都会代理其data对象中的所有属性:

var data = { a: 1 }
var vm = new Vue({
    data: data
})

vm.a === data.a // -> true

// setting the property also affects original data
vm.a = 2
data.a // -> 2

// ... and vice-versa
data.a = 3
vm.a // -> 3

需要注意的是只有代理属性是反应式的,如果在实例创建之后添加一个新的属性到实例上,将不会触发任何视图更新。关于这一点我们将在后续反应系统中讨论。

除了数据属性之外,Vue实例还提供了许多有用的实例属性和方法,这些属性和方法都以$开头以便和代理数据属性进行区分。例如:

var data = { a: 1 }
var vm = new Vue({
    el: '#example',
    data: data
})

vm.$data === data // -> true
vm.$el === document.getElementById('example') // -> true

// $watch is an instance method
vm.$watch('a', function (newVal, oldVal) {
    // this callback will be called when `vm.a` changes
})

1.2.2 实例生命周期

每个Vue实例在创建时都会经历一系列实例化步骤,例如,需要设置数据观察、编译模板、以及创建必要的数据绑定。在这个过程中,还会调用生命周期钩子,从而方便我们执行自定义逻辑,例如,created钩子会在实例创建后调用:

var vm = new Vue({
    data: {
        a: 1
    },
    created: function () {
        // `this` points to the vm instance
        console.log('a is: ' + this.a)
    }
})
// -> "a is: 1"

还有一些钩子会在实例生命周期的不同阶段调用,例如compiled、readydestroyed,所有被调用的生命周期钩子通过this指向调用它的Vue实例,一些用户可能会疑惑在Vue.js的世界中有没有“控制器”的概念,答案是没有。组件的自定义逻辑会被分割到这些生命周期钩子中。

1.2.3 props 用法

props将数据从父作用域传到子组件。在 Vue.js 中,父子组件的关系可以总结为 props down, events up 。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。

<div id="app-7">
  <ol>
    <!-- 现在我们为每个todo-item提供待办项对象    -->
    <!-- 待办项对象是变量,即其内容可以是动态的 -->
    <todo-item v-for="item in groceryList" v-bind:todo="item"></todo-item>
  </ol>
</div>


Vue.component('todo-item', {
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})
var app7 = new Vue({
  el: '#app-7',
  data: {
    groceryList: [
      { text: '蔬菜' },
      { text: '奶酪' },
      { text: '随便其他什么人吃的东西' }
    ]
  }
})

Vue.js学习
这只是一个假设的例子,但是我们已经设法将应用分割成了两个更小的单元,子单元通过 props 接口实现了与父单元很好的解耦。我们现在可以进一步为我们的 todo-item 组件实现更复杂的模板和逻辑的改进,而不会影响到父单元。

组件实例的作用域是孤立的。这意味着不能(也不应该)在子组件的模板内直接引用父组件的数据。要让子组件使用父组件的数据,我们需要通过子组件的props选项。

子组件要显式地用props 选项声明它期待获得的数据:

Vue.component('child', {
  // 声明 props
  props: ['message'],
  // 就像 data 一样,prop 可以用在模板内
  // 同样也可以在 vm 实例中像 “this.message” 这样使用
  template: '<span>{{ message }}</span>'
})

然后我们可以这样向它传入一个普通字符串:

<child message="hello!"></child>

结果:

hello!

1.2.4 data属性对象和函数返回对象的区别

我们先来看一下这个比较经典的问题,当初在学Vue的时候也犯过这样的迷惑,不知道何时传递data对象,何时传递data函数 。Vue.js的data是要一个对象还是一个function?

Vue 实例的数据对象。Vue.js 会递归地将它全部属性转为 getter/setter,从而让它能响应数据变化。这个对象必须是普通对象:原生对象,getter/setter 及原型属性会被忽略。不推荐观察复杂对象。

在实例创建之后,可以用 vm.$data 访问原始数据对象。Vue 实例也代理了数据对象所有的属性。

在定义组件时,同一定义将创建多个实例,此时 data 必须是一个函数,返回原始数据对象。如果 data 仍然是一个普通对象,则所有的实例将指向同一个对象!换成函数后,每当创建一个实例时,会调用这个函数,返回一个新的原始数据对象的副本。

简单说, 在实例中data是对象, 在组件中data就得是函数返回对象。

组件中的data写法示例:

<div id="example-2">
  <simple-counter></simple-counter>
</div>

var data = { counter: 0 }
Vue.component('simple-counter', {
  template: '<button v-on:click="counter += 1">{{ counter }}</button>',
  // 技术上 data 的确是一个函数了,因此 Vue 不会警告,
  // 但是我们返回给每个组件的实例的却引用了同一个data对象
  data: function () {
    return data
  }
})
new Vue({
  el: '#example-2'
})

由于这三个组件共享了同一个 data , 因此增加一个 counter 会影响所有组件!这不对。我们可以通过为每个组件返回全新的 data 对象来解决这个问题:

data: function () {
  return {
    counter: 0
  }
}

更多详情请参考:官网组件数据data传递说明(Component)

1.2.5 组件写法需注意的几个问题

一个组件下只能有一个并列的 div,可以这么写,所以复制官网示例的时候只要复制 div 里面的内容就好。

Vue.js学习

但是不能这样写:

Vue.js学习

第二。数据要写在 return 里面而不是像文档那样子写

Vue.js学习

错误的写法:

Vue.js学习

组件使用 :
firstComponent.vue

<template>
  <div id="firstcomponent">
    <h1>I am a title.</h1>
    <a> written by {{ author }} </a>
  </div>
</template>

<script type="text/javascript">
export default {
  data () {
    return {
      author: "微信公众号 jinkey-love"
    }
  }
}
</script>

<style>
</style>

其他Vue问题,可以参考该博文:Vue2.0 新手完全填坑攻略——从环境搭建到发布

2.Vue.js的常用指令请参考官方文档

Vue官方文档

1.v-model,v-for,v-on

在了解了vue的基本用法(数据绑定、指令、缩写、条件渲染等)后,来看一个完整示例,动态添加/删除元素:

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title></title>
        <link rel="stylesheet" href="styles/demo.css" />
    </head>

    <body>
        <div id="app">

            <fieldset>
                <legend>
                    Create New Person
                </legend>
                <div class="form-group">
                    <label>Name:</label>
                    <input type="text" v-model="newPerson.name"/>
                </div>
                <div class="form-group">
                    <label>Age:</label>
                    <input type="text" v-model="newPerson.age"/>
                </div>
                <div class="form-group">
                    <label>Sex:</label>
                    <select v-model="newPerson.sex">
                    <option value="Male">Male</option>
                    <option value="Female">Female</option>
                </select>
                </div>
                <div class="form-group">
                    <label></label>
                    <button @click="createPerson">Create</button>
                </div>
        </fieldset>
        <table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Age</th>
                    <th>Sex</th>
                    <th>Delete</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="person in people">
                    <td>{{ person.name }}</td>
                    <td>{{ person.age }}</td>
                    <td>{{ person.sex }}</td>
                    <td :class="'text-center'"><button @click="deletePerson($index)">Delete</button></td>
                    <td><button @click="greet">Greet</button></td>
                </tr>
            </tbody>
        </table>
        </div>
    </body>
    <script src="js/vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                newPerson: {
                    name: '',
                    age: 0,
                    sex: 'Male'
                },
                people: [{
                    name: 'Jack',
                    age: 30,
                    sex: 'Male'
                }, {
                    name: 'Bill',
                    age: 26,
                    sex: 'Male'
                }, {
                    name: 'Tracy',
                    age: 22,
                    sex: 'Female'
                }, {
                    name: 'Chris',
                    age: 36,
                    sex: 'Male'
                }]
            },
            methods:{
                createPerson: function(){
                    this.people.push(this.newPerson);
                    
                    // 添加完newPerson对象后,重置newPerson对象
                    this.newPerson = {name: '', age: 0, sex: 'Male'}
                },
                deletePerson: function(index){
                    // 删一个数组元素
                    this.people.splice(index,1);
                    alert(index);
                },

                greet:function(){
                    alert(this.newPerson.sex);
                }
            }
        })
    </script>

</html>

Vue.js学习

Demo
Github示例源码

实战示例:

添加、删除表单数据

<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Vue js</title>
<link rel="stylesheet" type="text/css" href="../assets/css/bootstrap.min.css">
</head>
<body>
  <header class="navbar navbar-fixed-top navbar-inverse">
    <div class="container">
    <h1>Vue开发</h1>
    </div>
  </header>

<div class="container" id="app" style="margin:100px;">
  <div class="row">
    <div class="col-md-offset-2 col-md-8">
      <div class="panel panel-default">
        <div class="panel-heading">
          welcome to Vue.js
        </div>
        <div class="panel-body">
          <h1>{{ message }}</h1>
          <!--
          <input type="text" class="form-control" v-model="message" />
          -->

          <ul class="list-group">
            <li class="list-group-item"
            v-for="(todo,index) in todos">
                {{ todo.id }} {{ todo.title }}
                <button class="btn btn-danger btn-sm pull-right"
                 v-on:click="deleteTodo(index)"
                >删除</button>
            </li>
          </ul>

          <form v-on:submit.prevent="addTodo(newTodo)">
            <div class="form-group">
                <input type="text" v-model="newTodo.title" class="form-control" placeholder="Add a list" />
            </div>
            <div class="form-group">
                <button class="btn btn-success">Add to do</button>
            </div>
          </form>

        </div>
      </div>
    </div>
  </div>
</div>
</body>

<!-- 注意:此处的引入文件需要放在body的后边,需要DOM加载完后,才能获取到#app-->
<script src="../assets/js/vue.js"></script>
<script>
  new Vue({
    el:'#app',
    data:{
      message:"Hello,Vue",
      todos:[
        {id:1,title:'Jack Chan'},
        {id:2,title:'Jet Lee'}
      ],
      newTodo:{id:null,title:""}
    },
    methods:{
      addTodo(newTodo){
        this.todos.push(newTodo)
        this.newTodo = {id:null,title:""}
      },
      deleteTodo(index){
        this.todos.splice(index,1)
      }
    }
  })

</script>
</html>

Vue.js学习

注意:在输入框输入数据添加到列表时,需要使用v-on:submit.prevent="addTodo(newTodo)方法对表单提交进行阻止,并使用v-model进行数据双向绑定,当输入框里的数据变化时,Vue实例中的newTodo:{id:null,title:""}属性数据也跟着变化,这样就可以将数据塞入到todos数组中。

2.计算属性computed,v-bind

  <!--  模板不再简单和清晰,所以,这里引入computed,增加代码可读性
          <h1>My todos {{todos.length}}</h1>
          -->
          <h1>My todos {{todosCount}}</h1>

JS中的用法:

 computed:{
      todosCount(){
        return this.todos.length;
      }

完整代码:

<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Vue js</title>
<link rel="stylesheet" type="text/css" href="../assets/css/bootstrap.min.css">
<style>
 .completed{
   color:#5c5b5c;
   text-decoration: line-through;
 }
</style>
</head>


<body>
  <header class="navbar navbar-fixed-top navbar-inverse">
    <div class="container">
    <h1>Vue开发</h1>
    </div>
  </header>

<div class="container" id="app" style="margin:100px;">
  <div class="row">
    <div class="col-md-offset-2 col-md-8">
      <div class="panel panel-default">
        <div class="panel-heading">
          welcome to Vue.js
        </div>
        <div class="panel-body">
          <!--  模板不再简单和清晰,所以,这里引入computed,增加代码可读性
          <h1>My todos {{todos.length}}</h1>
          -->
          <h1>My todos {{todosCount}}</h1>

          <ul class="list-group">
            <li class="list-group-item"
            v-bind:class="{ 'completed' : todo.completed }"
            v-for="(todo,index) in todos">
                {{ todo.id }} {{ todo.title }}
                <button class="btn btn-warning btn-xs pull-right"
                v-bind:class="[todo.completed ? 'btn-danger' : 'btn-success']"
                 v-on:click="toggleCompletion(todo)"
                >
                {{ todo.completed ? 'undo' : 'complete' }}
                </button>

                <button class="btn btn-danger btn-xs pull-right"
                 v-on:click="deleteTodo(index)"
                >删除</button>
            </li>
          </ul>

          <form v-on:submit.prevent="addTodo(newTodo)">
            <div class="form-group">
                <input type="text" v-model="newTodo.title" class="form-control" placeholder="Add a list" />
            </div>
            <div class="form-group">
                <button class="btn btn-success">Add to do</button>
            </div>
          </form>

        </div>
      </div>
    </div>
  </div>
</div>
</body>

<!-- 注意:此处的引入文件需要放在body的后边,需要DOM加载完后,才能获取到#app-->
<script src="../assets/js/vue.js"></script>
<script>
  new Vue({
    el:'#app',
    data:{
      message:"Hello,Vue",
      todos:[
        {id:1,title:'Jack Chan',completed:true},
        {id:2,title:'Jet Lee',completed:false}
      ],
      newTodo:{id:null,title:""}
    },
    computed:{
      todosCount(){
        return this.todos.length;
      }

    },
    methods:{
      addTodo(newTodo){
        this.todos.push(newTodo)
        this.newTodo = {id:null,title:""}
      },
      deleteTodo(index){
        this.todos.splice(index,1)
      },
      toggleCompletion(todo){
        todo.completed = !todo.completed;
      }
    }
  })

</script>


</html>

二、组件

组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。

组件化的好处:
增加了代码的可读性,更重要的是增加了代码的可重用性。

1)、全局组件

先注册,然后再使用

<div id="example">
  <my-component></my-component>
</div>

// 注册
Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})
// 创建根实例
new Vue({
  el: '#example'
})

渲染:

<div id="example">
  <div>A custom component!</div>
</div>

注意事项:组件在注册之后,便可以在父实例的模块中以自定义元素 <my-component></my-component> 的形式使用。要确保在初始化根实例 之前 注册了组件。

2)、局部注册

不必在全局注册每个组件。通过使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域中可用:

var Child = {
  template: '<div>A custom component!</div>'
}
new Vue({
  // ...
  components: {
    // <my-component> 将只在父模板可用
    'my-component': Child
  }
})

对上边的实战示例进行组件化封装处理:
本案例,完成两个组件化工作:
1:对列表进行组件封装,注意在组件模板属性参数的v-bind:todos="todos"传递
2:对表单进行了组装,注意data为function返回

注意在 JavaScript对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。

<!DOCTYPE html>

<!--
组件化的好处:
增加了代码的可读性,更重要的是增加了代码的可重用性。

本案例,完成两个组件化工作:
1:对列表进行组件封装,注意在组件模板属性参数的v-bind:todos="todos"传递
2:对表单进行了组装,注意data为function返回

注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。
-->
<head>
<meta charset="UTF-8">
<title>Vue js</title>
<link rel="stylesheet" type="text/css" href="../assets/css/bootstrap.min.css">
<style>
 .completed{
   color:#5c5b5c;
   text-decoration: line-through;
 }
</style>
</head>


<body>
  <header class="navbar navbar-fixed-top navbar-inverse">
    <div class="container">
    <h1>Vue开发</h1>
    </div>
  </header>

<div class="container" id="app" style="margin:100px;">
  <div class="row">
    <div class="col-md-offset-2 col-md-8">
      <div class="panel panel-default">
        <div class="panel-heading">
          welcome to Vue.js
        </div>
        <div class="panel-body">
          <!--  模板不再简单和清晰,所以,这里引入computed,增加代码可读性
          <h1>My todos {{todos.length}}</h1>
          -->
          <h1>My todos {{todosCount}}</h1>

          <!-- 这里使用组件化封装ul列表,这里需要给组件绑定一个数据属性,v-bind: 可以简写为一个冒号:-->
          <todo-items v-bind:todos="todos"></todo-items>
          <todo-form :todos="todos"></todo-form>

        </div>
      </div>
    </div>
  </div>
</div>
</body>

<!-- 列表组件 -->
<script type="text/x-template" id="todo-items-template">
  <ul class="list-group">
    <li class="list-group-item"
    v-bind:class="{ 'completed' : todo.completed }"
    v-for="(todo,index) in todos">
        {{ todo.id }} {{ todo.title }}
        <button class="btn btn-warning btn-xs pull-right"
        v-bind:class="[todo.completed ? 'btn-danger' : 'btn-success']"
         v-on:click="toggleCompletion(todo)"
        >
        {{ todo.completed ? 'undo' : 'complete' }}
        </button>

        <button class="btn btn-danger btn-xs pull-right"
         v-on:click="deleteTodo(index)"
        >删除</button>
    </li>
  </ul>
</script>

<!-- 表单组件 -->
<script type="text/x-template" id="add-form-template">
  <form v-on:submit.prevent="addTodo(newTodo)">
    <div class="form-group">
        <input type="text" v-model="newTodo.title" class="form-control" placeholder="Add a list" />
    </div>
    <div class="form-group">
        <button class="btn btn-success">Add to do</button>
    </div>
  </form>
</script>

<!-- 注意:此处的引入文件需要放在body的后边,需要DOM加载完后,才能获取到#app-->
<script src="../assets/js/vue.js"></script>
<script>

// 组件化-列表
Vue.component('todo-items',{
  template:'#todo-items-template',
  props:['todos'],   // 定义一个属性
  methods:{
    deleteTodo(index){
      this.todos.splice(index,1)
    },
    toggleCompletion(todo){
      todo.completed = !todo.completed;
    }
  }
})

// 组件化-表单
Vue.component('todo-form',{
  template:'#add-form-template',
  props:['todos'],

  // data为function返回
  data(){
    return{
      newTodo:{id:null,title:"",completed:false}
    }
  },
  methods:{
    addTodo(newTodo){
      this.todos.push(newTodo)
      this.newTodo = {id:null,title:"",completed:false}
    }
  }
})

  new Vue({
    el:'#app',
    data:{
      message:"Hello,Vue",
      todos:[
        {id:1,title:'Jack Chan',completed:true},
        {id:2,title:'Jet Lee',completed:false}
      ],
    },
    computed:{
      todosCount(){
        return this.todos.length;
      }

    }
  })

</script>
</html>

三、vue-cli脚手架

vue-cli脚手架Github地址:https://github.com/vuejs/vue-cli

1、node安装vue-cli脚手架

// 这里使用淘宝的镜像cnpm
$ cnpm install -g vue-cli  

安装好之后,可以通过vue命令查看:
Vue.js学习

2、命令安装webpack项目

➜  Code vue init webpack vuejs-cli

Vue.js学习

然后再执行上边给出的提示命令:

cd vuejs-2.0-cli
npm install
npm run dev

执行完上边的命令后,会打开浏览器的http://localhost:8080/#/页面

Vue.js学习

我们看一下这个目录下边的文件:

Vue.js学习

3.vue-router

vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。

vue-router 快速入门
Vue路由的使用

<div class="list-group">
    <a class="list-group-item" v-link="{ path: '/home'}">Home</a>
    <a class="list-group-item" v-link="{ path: '/about'}">About</a>
</div>

4.JavaScript ES6中export及export default的区别

相信很多人都使用过export、export default、import,然而它们到底有什么区别呢? 在JavaScript ES6中,export与export default均可用于导出常量、函数、文件、模块等,你可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用,但在一个文件或模块中,export、import可以有多个,export default仅有一个。

具体事例,请看下边的原文章:
JavaScript ES6中export及export default的区别

四、Vue请求api之vue-axios使用

axios github官方地址
vue-axios 扩展包GitHub地址

我们在学习vue的API请求,所以,我们用第二个特定的包vue-axios来安装https://github.com/imcvampire...

1.安装

npm install --save axios vue-axios

2.使用

安装好之后,引入到使用的文件中

import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'

Vue.use(VueAxios, axios)

示例:

Vue.axios.get(api).then((response) => {
  console.log(response.data)
})

this.axios.get(api).then((response) => {
  console.log(response.data)
})

五、laravel做后端API提供数据

这里使用axios请求接口,会出现跨域的问题,不过,我们可以通过安装https://github.com/barryvdh/laravel-cors库来解决这个问题。

六、Vuex学习

1、什么是Vuex?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。---官方文档

2、state理解

state 这样概念初次接触的时候可能会感觉到有点模糊,简单来说就是将 state 看成我们项目中使用的数据的集合。然后,Vuex 使得 组件本地状态(component local state)应用层级状态(application state) 有了一定的差异。

  • component local state:该状态表示仅仅在组件内部使用的状态,有点类似通过配置选项传入 Vue 组件内部的意思。

  • application level state:应用层级状态,表示同时被多个组件共享的状态层级。

假设有这样一个场景:我们有一个父组件,同时包含两个子组件。父组件可以很容易的通过使用 props 属性来向子组件传递数据。

但是问题来了,当我们的两个子组件如何和对方互相通信的? 或者子组件如何传递数据给他父组件的?在我们的项目很小的时候,这个两个问题都不会太难,因为我们可以通过事件派发和监听来完成父组件和子组件的通信。

然而,随着我们项目的增长:

  • 保持对所有的事件追踪将变得很困难。到底哪个事件是哪个组件派发的,哪个组件该监听哪个事件?

  • 项目逻辑分散在各个组件当中,很容易导致逻辑的混乱,不利于我们项目的维护。

  • 父组件将变得和子组件耦合越来越严重,因为它需要明确的派发和监听子组件的某些事件。

这就是 Vuex 用来解决的问题。 Vuex 的四个核心概念分别是:

  • The state tree:Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个『唯一数据源(SSOT)』而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

  • Getters:用来从 store 获取 Vue 组件数据。

  • Mutators:事件处理器用来驱动状态的变化。

  • Actions:可以给组件使用的函数,以此用来驱动事件处理器 mutations

如何你暂时还不太理解这个四个概念,不用着急,我们将在后面的项目实战中详细的解释。

Vuex 应用中数据的流向(Vuex 官方图)
Vue.js学习

上边的流程图简单解释下:

Vuex 规定,属于应用层级的状态只能通过 Mutation 中的方法来修改,而派发 Mutation 中的事件只能通过 action。

从左到又,从组件出发,组件中调用 action,在 action 这一层级我们可以和后台数据交互,比如获取初始化的数据源,或者中间数据的过滤等。然后在 action 中去派发 Mutation。Mutation 去触发状态的改变,状态的改变,将触发视图的更新。

注意事项

  • 数据流都是单向的

  • 组件能够调用 action

  • action 用来派发 Mutation

  • 只有 mutation 可以改变状态

  • store 是响应式的,无论 state 什么时候更新,组件都将同步更新

3、Vuex目录结构

我们来看一下创建的Vuex项目的目录结构:

Vue.js学习

  • components/ 文件夹用来存放我们的 Vue 组件

  • vuex/ 文件夹存放的是和 Vuex store 相关的东西(state object,actions,mutators)

  • build/ 文件是 webpack 的打包编译配置文件

  • config/ 文件夹存放的是一些配置项,比如我们服务器访问的端口配置等

  • dist/ 该文件夹一开始是不存在,在我们的项目经过 build 之后才会产出

  • App.vue 根组件,所有的子组件都将在这里被引用

  • index.html 整个项目的入口文件,将会引用我们的根组件 App.vue

  • main.js 入口文件的 js 逻辑,在 webpack 打包之后将被注入到 index.html 中

注:本博客Vuex部分内容转自该博文:使用 Vuex + Vue.js 构建单页应用,博文作者对Vuex理解的比较透彻,所以转过来学习下。

4、将上边实战列表表单例子用Vuex重构

Vue.js学习

先看Vuex的目录结构:

Vue.js学习

主要是在 src目录下做组件重构:

先看列表组件Todos.vue:

<template>
  <div id="todos">

  <ul class="list-group">
    <li class="list-group-item"
    v-bind:class="{ 'completed' : todo.completed }"
    v-for="(todo,index) in todos">
        {{ todo.id }}
        <router-link :to="{ name: 'todo', params: { id: todo.id }}">{{ todo.title }}</router-link>
        <button class="btn btn-warning btn-xs pull-right margin-right-10"
        v-bind:class="[todo.completed ? 'btn-danger' : 'btn-success']"
         v-on:click="toggleCompletion(todo)"
        >
        {{ todo.completed ? 'undo' : 'complete' }}
        </button>

        <button class="btn btn-danger btn-xs pull-right margin-right-10"
         v-on:click="deleteTodo(todo, index)"
        >删除</button>
    </li>
  </ul>

  <todo-form></todo-form>

</div>
</template>

<style>
  .completed{
    color:#5c5b5c;
    text-decoration: line-through;
  }
  .margin-right-10{
    margin-right: 10px;
  }
</style>

<script>
import TodoForm from './TodoForm';
  export default{
    name:'todos',
  //  props:['todos'],   // 定义一个属性
  computed: {
   todos() {
     return this.$store.state.todos
   }
 },
    methods:{
      deleteTodo(todo, index){
        this.$store.dispatch('removeTodo', todo, index)
      },
      toggleCompletion(todo){
        this.$store.dispatch('completeTodo', todo)
      }
    },
    components:{
        TodoForm
      }
  }
</script>

表单组件TodoForm.vue

<template>
  <form v-on:submit.prevent="addTodo(newTodo)">
     <div class="form-group">
         <input type="text" v-model="newTodo.title" class="form-control" placeholder="Add a list" />
     </div>
     <div class="form-group">
         <button class="btn btn-success" type="submit">Add to do</button>
     </div>
   </form>
</template>

<style>
  .completed{
    color:#5c5b5c;
    text-decoration: line-through;
  }
  .margin-right-10{
    margin-right: 10px;
  }
</style>

<script>
  export default{
    //  props:['todos'],
     // 子组件数据属性
     /*   Vuex 写法,将 newTodo 放入到 state 中
     data(){
       return{
         newTodo:{id:null,title:"",completed:false}
       }
     },*/
     computed: {
      newTodo() {
        return this.$store.state.newTodo
      }
    },
     methods:{
       addTodo(newTodo){
         // this.todos.push(newTodo)
         // this.newTodo = {id:null,title:"",completed:false}
           this.$store.dispatch('saveTodo', newTodo)
       }
     }
  }
</script>

App.vue总组件:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <!--
    <todos :todos="todos"></todos>
  -->

  <!-- 路由出口 -->
  <!-- 路由匹配到的组件将渲染在这里 -->
  <router-view></router-view>
  </div>
</template>

<script>
import Hello from './components/Hello';
import Todos from './components/Todos';
export default {
  name: 'app',
  /*
  data(){
    return {
      todos:[
        {id:1,title:'Jack Chan',completed:true},
        {id:2,title:'Jet Lee',completed:false}
      ]
    }
  },
  */
  // 异步请求
  mounted(){
    /*
    this.axios.get('http://baidu.com').then(response =>{
      console.log(response.data)
    })*/
    this.$store.dispatch('getTodos')
  },
  computed:{
    todosCount(){
      return this.$store.todos.length;
    }
  },
  components: {
    Hello
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

main.js文件:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'

// 引入axios请求API
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)

// 引入路由
import VueRouter from 'vue-router'
Vue.use(VueRouter)

// 引入vuex
import Vuex from 'vuex'
Vue.use(Vuex)

import Todos from './components/Todos';
import Todo from './components/Todo';

const routes = [
  { path: '/', component: Todos },
  { path: '/todo/:id', component: Todo, name:'todo'}
]

const router = new VueRouter({
  routes // (缩写)相当于 routes: routes
})


/* ====================== Vuex说明 ==================== //
  store:可以理解为一个大的容器
  state: 相当于一个全局变量数据属性
  mutations:里边的方法专门操作state里边的全局变量
  actions: 模型组件操作actions,获取数据,然后触发mutations
  小结:Vuex的核心,主要是理解其应用的场景,如果理清了这些思路,就可以很容易进行开发了。
 另外我们也可以将store这变量拆分为一个store.js文件,然后引用进来即可。
====================================================== */

// Vuex example
// store 可以理解为一个大的容器
const store = new Vuex.Store({

  state: {    // state 可以理解为全局的变量,可以在任意组件中使用
    todos: [],
    newTodo:{id:null, title:"", completed:false}
  },

  // mutations里边的方法用来修改state里的数据
  mutations: {
    get_todo_list(state, todos) {
      state.todos = todos;
    },
    complete_todo(state, todo){
      todo.completed = ! todo.completed
    },
    delete_todo(state, index){
      state.todos.splice(index, 1)
    },
    add_todo(state, todo){
      state.todos.push(todo)
    }
  },
  // actions 主要用来获取客户端的数据,然后将数据传给 mutations 的方法
  actions:{
    getTodos(store){
      store.commit('get_todo_list', [{id : 1, title:"hello,world!"}])
    },
    completeTodo(state, todo){
      store.commit('complete_todo', todo)
    },
    removeTodo(store, todo, index){
      store.commit('delete_todo', index)
    },
    saveTodo(store, todo){
      store.commit('add_todo', todo)
      store.state.newTodo = {id:null,title:"",completed:false}
    },


  }
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  template: '<App/>',
  components: { App },
  router
})

主页面 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>vuejs-2.0-cli</title>
    <link rel="stylesheet" href="https://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css">
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

本项目GitHub地址:
https://github.com/corwien/vu...


更多有关Vue.js学习的文章,请看这里:
Vue.js——60分钟快速入门
Vue.js——基于$.ajax实现数据的跨域增删查改
Vuejs2.0 文档攻略-介绍
使用 Vuex + Vue.js 构建单页应用
使用Vue.js和Vuex实现购物车场景
Vuex学习