vue


0、下载

npm install vue
npm install axios

然后将 dist 文件夹中的 axios.min.js 和 vue.min.js 放到 static/js中

<script src="../static/js/vue.min.js"></script>
<script src="../static/js/axios.min.js"></script>

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

基本结构

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>demo</title>
</head>
<body>
    <div id="app">

    </div>

<script src="../static/js/vue.min.js"></script>
<script src="../static/js/axios.min.js"></script>
<script>
    // 创建一个vue对象
    new Vue({
        el: '#app',//绑定vue作用的范围
        data: {//定义页面中显示的模型数据
            message: 'Hello Vue!'
        },
        created: function () {
            // `this` 指向 vm 实例
            console.log('a is: ' + this.a)
        },
        methods:{
            getData: function () {
                axios.get('https://api.coindesk.com/v1/bpi/currentprice.json')
                    .then(response => {
                        this.info = response.data.bpi
                    })
                    .catch(error => {
                        console.log(error);
                        this.errored = true
                    })
                    .finally(() => this.loading = false)
            }
        }
    })
</script>
</body>
</html>

一、代码模板

!生成html模板

vuehtml生成vue模板(需要进行设置)

<!-- id标识vue作用的范围 -->
<div id="app">
    <!-- {{}} 插值表达式,绑定vue中的data数据 -->
    {{ message }}
</div>
<script src="vue.min.js"></script>
<script>

    // 创建一个vue对象
    new Vue({
        el: '#app',//绑定vue作用的范围
        data: {//定义页面中显示的模型数据
            message: 'Hello Vue!'
        },
        methods:{

        }
    })

</script>

VSCode设置vue代码模板

{
    "vue htm": {
        "scope": "html",
        "prefix": "vuehtml",
        "body": [
            "<!DOCTYPE html>",
            "<html lang=\"en\">",
            "",
            "<head>",
            "    <meta charset=\"UTF-8\">",
            "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
            "    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">",
            "    <title>Document</title>",
            "</head>",
            "",
            "<body>",
            "    <div id=\"app\">",
            "",
            "    </div>",
            "    <script src=\"vue.min.js\"></script>",
            "    <script>",
            "        new Vue({",
            "            el: '#app',",
            "            data: {",
            "                $1",
            "            }",
            "        })",
            "    </script>",
            "</body>",
            "",
            "</html>",
        ],
        "description": "my vue template in html"
    }
}

二、基本语法

1、差值表达式

只能用在标签内容中,不能在标签的属性中

差值表达式
{{变量名}}
内容也可以是表达式
{{变量1 + '' + 变量2}}
也可以是方法,但必须有返回值
{{方法名()}}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- v-bind指令
            单向数据绑定
            这个指令一般用在标签属性里面,获取值
        -->
        <h1 v-bind:title="message">
            {{content}}
        </h1>

        <!--简写方式-->
        <h2 :title="message">
                {{content}}
            </h2>

    </div>
    <script src="vue.min.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                content: '我是标题',
                message: '页面加载于 ' + new Date().toLocaleString()
            }
        })
    </script>
</body>

</html>

2、指令

(1)v-bind:单向绑定

可简写为冒号:

<!-- 缩写 -->
<img :src="imageSrc">

<!-- 动态 attribute 名缩写 (2.6.0+) -->
<button :[key]="value"></button>

<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName">

<!-- class 绑定-->
<!--isRed和isBlue是一个布尔值,定义在vue中,通过改变该值设置标签class属性
如果isRed为true,则会添加red的class属性;为false则不添加 -->
<div :class="{ red:isRed }"></div>
<div :class="{ red:isRed,blue:isBlue }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]">

(2)v-model:双向绑定

双向数据绑定和单向数据绑定:使用 v-model 进行双向数据绑定

即在输入框中修改值,变量也会相应的修改

<body>
    <div id="app">
        <!--单向绑定-->
        <input type="text" v-bind:value="searchMap.keyWord"/>
        <!--双向绑定-->
        <input type="text" v-model="searchMap.keyWord"/>

        <p>{{searchMap.keyWord}}</p>

    </div>
    <script src="vue.min.js"></script>
    <script>

        new Vue({
            el: '#app',
            data: {
                searchMap:{
                    keyWord: 'rewind'
                }
            }
        })
    </script>
</body>

结合单选框,多选框

<div id="app">
  <!--1.checkbox单选框-->
  <!--<label for="agree">-->
    <!--<input type="checkbox" id="agree" v-model="isAgree">同意协议-->
  <!--</label>-->
  <!--<h2>您选择的是: {{isAgree}}</h2>-->
  <!--<button :disabled="!isAgree">下一步</button>-->

  <!--2.checkbox多选框,选中的条目会自动添加到hobbies数组中,取消选择自动删除-->
  <input type="checkbox" value="篮球" v-model="hobbies">篮球
  <input type="checkbox" value="足球" v-model="hobbies">足球
  <input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球
  <input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球
  <h2>您的爱好是: {{hobbies}}</h2>

  <!-- 提升选择范围,单击文字 也可以选中 -->  
  <label v-for="item in originHobbies" :for="item">
    <input type="checkbox" :value="item" :id="item" v-model="hobbies">{{item}}
  </label>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      isAgree: false, // 单选框
      hobbies: [], // 多选框,
      originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '高尔夫球']
    }
  })
</script>
修饰符
  • lazy修饰符:

默认情况下,v-model默认是在input事件中同步输入框的数据的。

也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。

lazy修饰符可以让数据在失去焦点或者回车时才会更新:

  • number修饰符:

    默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。

    但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。

    number修饰符可以让在输入框中输入的内容自动转成数字类型:

  • trim修饰符:

如果输入的内容首尾有很多空格,通常我们希望将其去除

trim修饰符可以过滤内容左右两边的空格

<div id="app">
  <!--1.修饰符: lazy-->
  <input type="text" v-model.lazy="message">
  <h2>{{message}}</h2>


  <!--2.修饰符: number-->
  <input type="number" v-model.number="age">
  <h2>{{age}}-{{typeof age}}</h2>

  <!--3.修饰符: trim-->
  <input type="text" v-model.trim="name">
  <h2>您输入的名字:{{name}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      age: 0,
      name: ''
    }
  })

  var age = 0
  age = '1111'
  age = '222'
</script>

(3)v-once:单次渲染

只渲染元素和组件一次。随后的重新渲染(数据改变),元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 有子元素 -->
<div v-once>
  <h1>comment</h1>
  <p>{{msg}}</p>
</div>
<!-- 组件 -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` 指令-->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>

(4)v-html:解析html解析

可将差值表达式中的html标签解析并渲染出来

<div v-html="html">{{myHtml}}</div>

(5)v-pre:放弃解析

不解析标签中的内容,包括差值表达式,会原封不动输出

跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。

<span v-pre>{{ this will not be compiled }}</span>

(6)v-if:条件判断

v-else-if 和 v-else 限制:前一兄弟元素必须有 v-ifv-else-if

key属性是用来解决切换input标签时,用户输入内容没清空的现象。key的值必须不相同

<div id="app">
  <span v-if="isUser">
    <label for="username">用户账号</label>
    <input type="text" id="username" placeholder="用户账号" key="username">
  </span>
  <span v-else>
    <label for="email">用户邮箱</label>
    <input type="text" id="email" placeholder="用户邮箱" key="email">
  </span>
  <button @click="isUser = !isUser">切换类型</button>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      isUser: true
    }
  })
</script>

(7)v-show:是否显示

用法效果基本同 v-if

区别:如果条件为 false 时:

v-if:所在的标签不渲染,即不存在

v-show:所在标签增加 style="display:none;" 该css属性将标签隐藏,即不显示,但却是存在的

如何选择

如果在显示和隐藏切换次数超过一次时,使用 v-show

使用 v-if 可以结合 v-else 和 v-else-if (用的比较多)

(8)v-for:遍历

遍历数组

建议添加 :key="item" ,提高性能,但必须保证key的值唯一不重复

<div id="app">
  <!--1.在遍历的过程中,没有使用索引值(下标值)-->
  <ul>
    <li v-for="item in names" :key="item">{{item}}</li>
  </ul>

  <!--2.在遍历的过程中, 获取索引值-->
  <ul>
    <li v-for="(item, index) in names" :key="item">
      {{index+1}}.{{item}}
    </li>
  </ul>

</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      names: ['why', 'kobe', 'james', 'curry']
    }
  })
</script>

关于响应式:

通过索引值修改数组中的元素,不是响应式的,即改变数组内容,vue的显示不会更新

1-6的方法都是响应式的

// 1.push(): 添加元素
this.letters.push('aaa')
this.letters.push('aaaa', 'bbbb', 'cccc')

// 2.pop(): 删除数组中的最后一个元素
this.letters.pop();

// 3.shift(): 删除数组中的第一个元素
this.letters.shift();

// 4.unshift(): 在数组最前面添加元素
this.letters.unshift()
this.letters.unshift('aaa', 'bbb', 'ccc')

// 5.splice作用: 删除元素/插入元素/替换元素
// 第一个参数,开始操作的索引
// 删除元素: 第二个参数传入你要删除几个元素(如果没有传,就删除后面所有的元素)
// 替换元素: 第二个参数, 表示我们要替换几个元素, 后面是用于替换前面的元素
// 插入元素: 第二个参数, 传入0, 并且后面跟上要插入的元素
// splice(start)
// splice(start):
this.letters.splice(1, 3, 'm', 'n', 'l', 'x')
this.letters.splice(1, 0, 'x', 'y', 'z')

// 5.sort():排序
this.letters.sort()

// 6.reverse():反转
this.letters.reverse()

// --------------------------------------------------------------
// 注意: 通过索引值修改数组中的元素,不是响应式的
this.letters[0] = 'bbbbbb';
// 可用以下2种方法,响应式的修改数组的值
this.letters.splice(0, 1, 'bbbbbb')

// set(要修改的对象, 索引值, 修改后的值)
Vue.set(this.letters, 0, 'bbbbbb')

遍历对象(用的少)

<div id="app">
  <!--1.在遍历对象的过程中, 如果只是获取一个值, 那么获取到的是value-->
  <ul>
    <li v-for="item in info">{{item}}</li>
  </ul>

  <!--2.获取key和value 格式: (value, key) -->
  <ul>
    <li v-for="(value, key) in info">{{value}}-{{key}}</li>
  </ul>

  <!--3.获取key和value和index 格式: (value, key, index) -->
  <ul>
    <li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
  </ul>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      info: {
        name: 'why',
        age: 18,
        height: 1.88
      }
    }
  })
</script>

页面显示

why
18
1.88

why-name
18-age
1.88-height

why-name-0
18-age-1
1.88-height-2

3、条件渲染

v-if:条件指令

注意:单个复选框绑定到布尔值

<body>
    <div id="app">
        <input type="checkbox" v-model="ok"/>是否同意
        <!--条件指令 v-if  v-else -->
        <h1 v-if="ok">尚硅谷</h1>
        <h1 v-else>谷粒学院</h1>
    </div>
    <script src="vue.min.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                ok:false
            }
        })
    </script>
</body>

v-show:条件指令

使用v-show完成和上面相同的功能

  • v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
  • v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
  • 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
  • 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

4、列表循环渲染

v-for:列表循环指令

<body>
    <div id="app">
        <ul>
            <!--n为变量,将1到10循环赋值给n -->
            <li v-for="n in 10"> {{n}} </li>
        </ul>
        <ol>
            <!-- n为变量,index为索引-->
            <li v-for="(n,index) in 10">{{n}} -- {{index}}</li>
        </ol>

        <hr/>
        <table border="1">
            <!-- 将userlist里的值循环赋值给user,即遍历userList-->
            <tr v-for="user in userList">
                <td>{{user.id}}</td>
                <td>{{user.username}}</td>
                <td>{{user.age}}</td>
            </tr>
        </table>

    </div>
    <script src="vue.min.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                userList: [
                        { id: 1, username: 'helen', age: 18 },
                        { id: 2, username: 'peter', age: 28 },
                        { id: 3, username: 'andy', age: 38 }
                    ]
            }
        })
    </script>
</body>

5、计算属性

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。

所以,对于任何复杂逻辑,你都应当使用计算属性

<div id="app">
  <h2>{{firstName + ' ' + lastName}}</h2>
  <h2>{{firstName}} {{lastName}}</h2>

  <h2>{{getFullName()}}</h2>

  <h2>{{fullName}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      firstName: 'Lebron',
      lastName: 'James'
    },
    // computed: 计算属性(),如果多次调用其实只执行一次
    computed: {
      fullName: function () {
        return this.firstName + ' ' + this.lastName
      }
    },
    methods: {
      getFullName() {
        return this.firstName + ' ' + this.lastName
      }
    }
  })
</script>

你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 firstNamelastName 依赖于 fullName,因此当 firstNamelastName发生改变时,所有依赖 fullName 的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 fullName 计算属性会立即返回之前的计算结果,而不必再次执行函数。

这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

computed: {
  now: function () {
    return Date.now()
  }
}

相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。

(1)与侦听属性对比

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

<div id="demo">{{ fullName }}</div>
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

(2)setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstNamevm.lastName 也会相应地被更新。

6、侦听属性

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

例如:

<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
<script>
var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: 'I cannot give you an answer until you ask a question!'
  },
  watch: {
    // 如果 `question` 发生改变,这个函数就会运行
    question: function (newQuestion, oldQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.debouncedGetAnswer()
    }
  }
  // ....  
})
</script>

在这个示例中,使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

除了 watch 选项之外,您还可以使用命令式的 vm.$watch API

7、事件

需求:点击查询按钮,按照输入框中输入的内容查找公司相关信息

在前面的例子基础上,data节点中增加 result,增加 methods节点 并定义 search方法

html中增加 button 和 p

使用 v-on 进行数件处理,v-on:click 表示处理鼠标点击事件,事件调用的方法定义在 vue 对象声明的 methods 节点中

<body>
    <div id="app">
        <!--vue绑定事件-->
        <button v-on:click="search()">查询</button>

        <!--vue绑定事件简写-->
        <button @click="search()">查询1</button>

        <!-- 在调用方式, 如何手动的获取到浏览器参数的event对象: $event-->
          <button @click="btn3Click(abc, $event)">按钮3</button>
    </div>
    <script src="vue.min.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                searchMap:{
                    keyWord: 'rewind'
                },
                //查询结果
                result: {}
            },
            methods:{//定义多个方法
                search() {
                    console.log('search....')
                },
                f1() {
                    console.log('f1...')
                },
                btn3Click(abc, event) {
                    console.log('++++++++', abc, event);
                }
            }
        })
    </script>
</body>

事件修饰符的使用

修饰符 (Modifiers) 是以半角句号(.)指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。

例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault():

即阻止事件原本的默认行为

<body>
    <div id="app">
        <!--.prevent 阻止默认事件 -->
        <form action="save" v-on:submit.prevent="onSubmit">
            <input type="text" id="name" v-model="user.username"/>
            <button type="submit">保存</button>
        </form>

          <!--.stop修饰符的使用,防止时间冒泡,即点击内部按钮div的点击事件也会执行-->
        <div @click="divClick">
            aaaaaaa
            <button @click.stop="btnClick">按钮</button>
        </div>

      <!--3. .监听某个键盘的键帽,此处.enter是监听回车-->
      <input type="text" @keyup.enter="keyUp()">

      <!--4. .once修饰符的使用,只触发一次-->
      <button @click.once="btn2Click()">按钮2</button>
    </div>
    <script src="vue.min.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                user:{}
            },
            methods:{
                onSubmit() {
                    if (this.user.username) {
                        console.log('提交表单')
                    } else {
                        alert('请输入用户名')
                    }
                },
                keyUp() {
                    console.log('keyUp');
                },
                btn2Click() {
                    console.log('btn2Click');
                }
            }
        })
    </script>
</body>

8、过滤器

用于数据的过滤

<div id="app">
  <div v-if="books.length">
    <table>
      <thead>
      <tr>
        <th></th>
        <th>书籍名称</th>
        <th>出版日期</th>
        <th>价格</th>
        <th>购买数量</th>
        <th>操作</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="(item, index) in books">
        <td>{{item.id}}</td>
        <td>{{item.name}}</td>
        <td>{{item.date}}</td>
        <td>{{item.price | showPrice}}</td>
        <td>
          <button @click="decrement(index)" v-bind:disabled="item.count <= 1">-</button>
          {{item.count}}
          <button @click="increment(index)">+</button>
        </td>
        <td><button @click="removeHandle(index)">移除</button></td>
      </tr>
      </tbody>
    </table>
      <!-- 过滤器的使用 -->
    <h2>总价格: {{totalPrice | showPrice}}</h2>
  </div>
  <h2 v-else>购物车为空</h2>
</div>
<script>
const app = new Vue({
  el: '#app',
  data: {
    books: [
      {
        id: 1,
        name: '《算法导论》',
        date: '2006-9',
        price: 85.00,
        count: 1
      },
      {
        id: 2,
        name: '《UNIX编程艺术》',
        date: '2006-2',
        price: 59.00,
        count: 1
      },
      {
        id: 3,
        name: '《编程珠玑》',
        date: '2008-10',
        price: 39.00,
        count: 1
      },
      {
        id: 4,
        name: '《代码大全》',
        date: '2006-3',
        price: 128.00,
        count: 1
      },
    ]
  },
  methods: {
    // getFinalPrice(price) {
    //   return '¥' + price.toFixed(2)
    // }
    increment(index) {
      this.books[index].count++
    },
    decrement(index) {
      this.books[index].count--
    },
    removeHandle(index) {
      this.books.splice(index, 1)
    }
  },
  computed: {
    totalPrice() {
      let totalPrice = 0
      for (let i = 0; i < this.books.length; i++) {
        totalPrice += this.books[i].price * this.books[i].count
      }
      return totalPrice

      // for (let i in/of this.books)
      // reduce
    }
  },
    // 定义过滤器
  filters: {
    showPrice(price) {
      return '¥' + price.toFixed(2)
    }
  }
})
</script>

三、生命周期

组件生命周期图示

1、beforeCreate

在组件实例初始化完成之后立即调用。

  • 详细信息

    会在实例初始化完成、props 解析之后、data()computed 等选项处理之前立即调用。

    注意,组合式 API 中的 setup() 钩子会在所有选项式 API 钩子之前调用,beforeCreate() 也不例外。

2、created

在组件实例处理完所有与状态相关的选项后调用。

  • 详细信息

    当这个钩子被调用时,以下内容已经设置完成:响应式数据、计算属性、方法和侦听器。然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。

3、beforeMount

在组件被挂载之前调用。

  • 详细信息

    当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。

    这个钩子在服务端渲染时不会被调用。

4、mounted

在组件被挂载之后调用。

  • 详细信息

    组件在以下情况下被视为已挂载:

    • 所有同步子组件都已经被挂载。(不包含异步组件或 <Suspense> 树内的组件)
    • 其自身的 DOM 树已经创建完成并插入了父容器中。注意仅当根容器在文档中时,才可以保证组件 DOM 树也在文档中。

    这个钩子通常用于执行需要访问组件所渲染的 DOM 树相关的副作用,或是在服务端渲染应用中用于确保 DOM 相关代码仅在客户端被调用。

    这个钩子在服务端渲染时不会被调用。

5、beforeUpdate

在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。

  • 详细信息

    这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。

    这个钩子在服务端渲染时不会被调用。

6、updated

在组件因为一个响应式状态变更而更新其 DOM 树之后调用。

  • 详细信息

    父组件的更新钩子将在其子组件的更新钩子之后调用。

    这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。

    这个钩子在服务端渲染时不会被调用。

    不要在 updated 钩子中更改组件的状态,这可能会导致无限的更新循环!

7、beforeUnmount

在一个组件实例被卸载之前调用。

  • 详细信息

    当这个钩子被调用时,组件实例依然还保有全部的功能。

    这个钩子在服务端渲染时不会被调用。

8、unmounted

在一个组件实例被卸载之后调用。

  • 详细信息

    一个组件在以下情况下被视为已卸载:

    • 其所有子组件都已经被卸载。
    • 所有相关的响应式作用 (渲染作用以及 setup() 时创建的计算属性和侦听器) 都已经停止。

    可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。

    这个钩子在服务端渲染时不会被调用。

9、errorCaptured

在捕获了后代组件传递的错误时调用。

  • 详细信息

    错误可以从以下几个来源中捕获:

    • 组件渲染
    • 事件处理器
    • 生命周期钩子
    • setup() 函数
    • 侦听器
    • 自定义指令钩子
    • 过渡钩子

    这个钩子带有三个实参:错误对象、触发该错误的组件实例,以及一个说明错误来源类型的信息字符串。

    你可以在 errorCaptured() 中更改组件状态来为用户显示一个错误状态。然而重要的是,不要让错误状态渲染为导致本次错误的内容,否则组件就会进入无限的渲染循环中。

    这个钩子可以通过返回 false 来阻止错误继续向上传递。请看下方的传递细节介绍。

    错误传递规则

    • 默认情况下,所有的错误都会被发送到应用级的 app.config.errorHandler (前提是这个函数已经定义),这样这些错误都能在一个统一的地方报告给分析服务。
    • 如果组件的继承链或组件链上存在多个 errorCaptured 钩子,对于同一个错误,这些钩子会被按从底至上的顺序一一调用。这个过程被称为“向上传递”,类似于原生 DOM 事件的冒泡机制。
    • 如果 errorCaptured 钩子本身抛出了一个错误,那么这个错误和原来捕获到的错误都将被发送到 app.config.errorHandler
    • errorCaptured 钩子可以通过返回 false 来阻止错误继续向上传递。即表示“这个错误已经被处理了,应当被忽略”,它将阻止其他的 errorCaptured 钩子或 app.config.errorHandler 因这个错误而被调用。

10、activated

若组件实例是 <keepalive> 缓存树的一部分,当组件被插入到 DOM 中时调用。

即组件缓存,新建组件,或从其他页面返回缓存组件时触发

这个钩子在服务端渲染时不会被调用。

11、deactivated

若组件实例是 <keepalive>缓存树的一部分,当组件从 DOM 中被移除时调用。

这个钩子在服务端渲染时不会被调用。

四、组件

1、组件注册

一般只需要用到局部组件

(1)原始方式

<div id="app">
  <cpn2></cpn2>
</div>

<script src="../js/vue.js"></script>
<script>
  // 1.创建第一个组件构造器(子组件)
  const cpnC1 = Vue.extend({
    template: `
      <div>
        <h2>我是标题1</h2>
        <p>我是内容, 哈哈哈哈</p>
      </div>
    `
  })


  // 2.创建第二个组件构造器(父组件)
  const cpnC2 = Vue.extend({
    template: `
      <div>
        <h2>我是标题2</h2>
        <p>我是内容, 呵呵呵呵</p>
        <cpn1></cpn1>
      </div>
    `,
    components: {
      cpn1: cpnC1
    }
  })

  // root组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn2: cpnC2
    }
  })
</script>

(2)语法糖

<div id="app">
  <cpn1></cpn1>
  <cpn2></cpn2>
</div>

<script src="../js/vue.js"></script>
<script>
  // 全局组件注册
  Vue.component('cpn1', {
    template: `
      <div>
        <h2>我是标题1</h2>
        <p>我是内容, 哈哈哈哈</p>
      </div>
    `
  })

  // 2.注册局部组件的语法糖
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    // 局部组件注册
    components: {
      'cpn2': {
        template: `
          <div>
            <h2>我是标题2</h2>
            <p>我是内容, 呵呵呵</p>
          </div>
    `
      }
    }
  })
</script>

(3)组件抽离

<div id="app">
  <cpn></cpn>
  <cpn2></cpn2>
</div>

<!--方式1.script标签, 注意:类型必须是text/x-template-->
<!--<script type="text/x-template" id="cpn">-->
<!--<div>-->
  <!--<h2>我是标题</h2>-->
  <!--<p>我是内容,哈哈哈</p>-->
<!--</div>-->
<!--</script>-->

<!--方式2.template标签-->
<template id="cpn">
  <div>
    <h2>我是标题</h2>
    <p>我是内容,呵呵呵</p>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>

  // 1.注册一个全局组件
  Vue.component('cpn', {
    template: '#cpn'
  });

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    // 局部组件
    components: {
      'cpn2': {
        template: `#cpn`
      }
    }
  })
</script>

2、组件通信

(1)父传子-props

将父组件的 messagemovies 传递到子组件中.

注意:html标签不区分大小写,所以定义 cMovies 后绑定值时使用 :c-movies ,子组件内部同样可以使用 cMovies

<body>

<!-- 父组件 -->    
<div id="app">
    <!--注意驼峰要改为- -->
  <cpn :cmessage="message" :c-movies="movies"></cpn>
</div>

<!-- 子组件 -->    
<template id="cpn">
  <div>
    <ul>
      <li v-for="item in cMovies">{{item}}</li>
    </ul>
    <h2>{{cmessage}}</h2>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  // 父传子: props
  const cpn = {
    template: '#cpn',
    // 方式一、直接声明要传递的参数名
    // props: ['cmovies', 'cmessage'],
    // 方式二、可对传递的参数进行类型的限制,默认值
    props: {
      // 1.类型限制
      // cmovies: Array,
      // cmessage: String,

      // 2.提供一些默认值, 以及必传值
      cmessage: {
        type: String,
        default: 'aaaaaaaa',
        required: true
      },
      // 类型是对象或者数组时, 默认值必须是一个函数
      cMovies: {
        type: Array,
        default() {
          return []
        }
      }
    },

    data() {
      return {}
    },
    methods: {

    }
  };

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      movies: ['海王', '海贼王', '海尔兄弟']
    },
    components: {
      cpn
    }
  })
</script>

(2)子传父-自定义事件

自定义事件的流程:

  • 在子组件中,通过$emit()来触发事件。 this.$emit('传递事件名',传递数据1,传递数据2...)

  • 在父组件中,通过v-on来监听子组件事件。n自定义事件的流程:

<子组件标签 @子组件传递事件名="父组件自定义方法"></子组件标签>

==================
methods:{
    父组件自定义方法(子传递数据1,子传递数据2){
        。。。。。。。
    }
}
<!--父组件模板-->
<div id="app">
  <!--接收子组件的事件,默认将参数传递过来了-->
  <cpn @item-click="cpnClick"></cpn>
<!--  <cpn @item-click="cpnClick(item)"></cpn>-->
</div>

<!--子组件模板-->
<template id="cpn">
  <div>
    <button v-for="item in categories"
            @click="btnClick(item)">
      {{item.name}}
    </button>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>

  // 1.子组件
  const cpn = {
    template: '#cpn',
    data() {
      return {
        categories: [
          {id: 'aaa', name: '热门推荐'},
          {id: 'bbb', name: '手机数码'},
          {id: 'ccc', name: '家用家电'},
          {id: 'ddd', name: '电脑办公'},
        ]
      }
    },
    methods: {
      btnClick(item) {
        // 发射事件: 自定义事件
        this.$emit('item-click', item)
      }
    }
  }

  // 2.父组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn
    },
    methods: {
      // 处理子组件发送过来的事件
      cpnClick(item) {
        console.log('cpnClick', item);
      }
    }
  })
</script>

3、组件间访问方式

(1)父访问子

$refs :用的多,通过 ref="aaa"this.$refs.aaa 获取组件

$children :少用,采用数组存储所有子组件,用索引获取组件,如果再添加组件,则会乱套

<div id="app">
  <cpn></cpn>
  <cpn></cpn>
  <cpn ref="aaa"></cpn>
  <button @click="btnClick">按钮</button>
</div>

<template id="cpn">
  <div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    methods: {
      btnClick() {
        // 1.$children  通过索引获取子组件,少用
        // console.log(this.$children);
        // for (let c of this.$children) {
        //   console.log(c.name);
        //   c.showMessage();
        // }
        // console.log(this.$children[3].name);

        // 2.$refs => 对象类型, 默认是一个空的对象 组件标签需加ref='aaa'
        console.log(this.$refs.aaa.name);
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            name: '我是子组件的name'
          }
        },
        methods: {
          showMessage() {
            console.log('showMessage');
          }
        }
      },
    }
  })
</script>

(2)子访问父/根

<div id="app">
  <cpn></cpn>
</div>

<template id="cpn">
  <div>
    <h2>我是cpn组件</h2>
    <ccpn></ccpn>
  </div>
</template>

<template id="ccpn">
  <div>
    <h2>我是子组件</h2>
    <button @click="btnClick">按钮</button>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            name: '我是cpn组件的name'
          }
        },
        components: {
          ccpn: {
            template: '#ccpn',
            methods: {
              btnClick() {
                // 1.访问父组件$parent
                // console.log(this.$parent);
                // console.log(this.$parent.name);

                // 2.访问根组件$root
                console.log(this.$root);
                console.log(this.$root.message);
              }
            }
          }
        }
      }
    }
  })
</script>

五、插槽

1、基本使用

<!--
1.插槽的基本使用 <slot></slot>
2.插槽的默认值 <slot>button</slot>
3.如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素
-->

<div id="app">
  <cpn></cpn>

  <cpn><span>哈哈哈</span></cpn>
  <cpn><i>呵呵呵</i></cpn>
  <cpn>
    <i>呵呵呵</i>
    <div>我是div元素</div>
    <p>我是p元素</p>
  </cpn>

  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>

<template id="cpn">
  <div>
    <h2>我是组件</h2>
    <p>我是组件, 哈哈哈</p>
    <slot><button>按钮</button></slot>
    <!--<button>按钮</button>-->
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn'
      }
    }
  })
</script>

2、具名插槽(常用)

<div id="app">
  <cpn><span slot="center">标题</span></cpn>
  <cpn><button slot="left">返回</button></cpn>
</div>

<template id="cpn">
  <div>
    <slot name="left"><span>左边</span></slot>
    <slot name="center"><span>中间</span></slot>
    <slot name="right"><span>右边</span></slot>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn'
      }
    }
  })
</script>

3、插槽数据子传父

<div id="app">
  <cpn></cpn>

  <cpn>
    <!--目的是获取子组件中的pLanguages-->
    <template slot-scope="slot">
      <!--<span v-for="item in slot.data"> - {{item}}</span>-->
      <span>{{slot.data.join(' - ')}}</span>
    </template>
  </cpn>

  <cpn>
    <!--目的是获取子组件中的pLanguages-->
    <template slot-scope="slot">
      <!--<span v-for="item in slot.data">{{item}} * </span>-->
      <span>{{slot.data.join(' * ')}}</span>
    </template>
  </cpn>
  <!--<cpn></cpn>-->
</div>

<template id="cpn">
  <div>
    <slot :data="pLanguages">
      <ul>
        <li v-for="item in pLanguages">{{item}}</li>
      </ul>
    </slot>
  </div>
</template>
<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']
          }
        }
      }
    }
  })
</script>

六、模块化

1、导出

var name = '小明'
var age = 18
var flag = true

function sum(num1, num2) {
  return num1 + num2
}

if (flag) {
  console.log(sum(20, 30));
}

// 1.导出方式一:
export {
  flag, sum
}

// 2.导出方式二:
export var num1 = 1000;
export var height = 1.88


// 3.导出函数/类
export function mul(num1, num2) {
  return num1 * num2
}

export class Person {
  run() {
    console.log('在奔跑');
  }
}

// 5.export default  :由导入者命名,并且参数只能导出一个
/*
const address1 = '北京市'
export {
  address1
}

export const address1 = '北京市'

const address2 = '北京市'
export default address2
*/

export default function (argument) {
  console.log(argument);
}

2、导入

// 1.导入的{}中定义的变量
import {flag, sum} from "./aaa.js";

if (flag) {
  console.log('小明是天才, 哈哈哈');
  console.log(sum(20, 30));
}

// 2.直接导入export定义的变量
import {num1, height} from "./aaa.js";

console.log(num1);
console.log(height);

// 3.导入 export的function/class
import {mul, Person} from "./aaa.js";

console.log(mul(30, 50));

const p = new Person();
p.run()

// 4.导入 export default中的内容
import addr from "./aaa.js";

addr('你好啊');

// 5.统一全部导入
// import {flag, num, num1, height, Person, mul, sum} from "./aaa.js";

import * as aaa from './aaa.js'

console.log(aaa.flag);
console.log(aaa.height);

七、脚手架

1、webpack

webpack核心就是让我们可以进行模块化开发,并且会帮助我们处理模块间的依赖关系。

而且不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用。

安装

需要node环境

全局安装:

npm install webpack@3.6.0 -g

查看安装版本:

webpack --version

局部安装:–save-dev`是开发时依赖,项目打包后不需要继续使用的。

cd 对应目录
npm install webpack@3.6.0 --save-dev

为什么全局安装后,还需要局部安装呢?

在终端直接执行webpack命令,使用的全局安装的webpack

当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack

2、vue cli

(1)安装/初始化

需要 node 14及以上

vue cli:vue脚手架安装

npm install -g @vue/cli

注意:上面安装的是Vue CLI3的版本,如果需要想按照Vue CLI2的方式初始化项目时不可以的

image-20210930223212718

Vue CLI2初始化项目

vue init webpack my-project

Vue CLI3初始化项目

vue create my-project

初始化选项

image-20210930223416296

(2)目录结构

vue cli2 目录结构

image-20210930223932248

vue cli3 目录结构

image-20210930223902524

(3)运行、打包

运行

npm run dev

打包

npm run build

3、vue2版本

npm install -g vue-cli
veu init webapck my-project

八、vue-Router

0、基本说明

vue-router是基于路由和组件

路由用于设定访问路径, 将路径和组件映射起来.

在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.

安装:

npm install vue-router --save

基本流程

<router-link>: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个<a>标签.

<router-view>: 该标签会根据当前的路径, 动态渲染出不同的组件.

网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和<router-view>处于同一个等级.

在路由切换时, 切换的是<router-view>挂载的组件, 其他内容不会发生改变.

image-20210715224555795

效果图

image-20210715224907460

(1)、创建 router 实例 index.js

在目录 src/router/index.js

// 配置路由相关的信息
import VueRouter from 'vue-router'
import Vue from 'vue'

import Home from '../components/Home'
import About from '../components/About'

// 1.通过Vue.use(插件), 安装插件
Vue.use(VueRouter)

// 2.创建VueRouter对象,配置路由
const routes = [
  {
    // 默认重定向到首页,也就是下面的/home映射中,''或'/'  
    path: '',
    // redirect重定向
    redirect: '/home'
  },
  {
    path: '/home',
    // 指向上方引入的组件
    component: Home
  },
  {
    path: '/about',
    component: About
  },
  {
    // 动态路由,路径属性,id
    path: '/user/:id',
    component: User,
    meta: {
      title: '用户'
    },
  }  
]
const router = new VueRouter({
  // 配置路由和组件之间的应用关系
  routes,
  // 配置改变路径的方式为 history ,可去除地址栏上的#
  mode: 'history',
  // 配置:只有<router-link>在匹配到时才会出现该class  
  linkActiveClass: 'active'
})

// 3.将router对象传入到Vue实例
export default router

(2)、挂载 main.js

src/main.js

import Vue from 'vue'
import App from './App'
// 引入 router
import router from './router'

Vue.config.productionTip = false

new Vue({
  el: '#app',
  // 挂载到vue实例  
  router,
  render: h => h(App)
})

(3)、使用 app.vue

<template>
  <div id="app">
    <h2>我是APP组件</h2>
    <!-- 路由跳转按钮,前两个为文字型,后两个为按钮型 -->
    <!-- replace属性作用:页面不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个页面中 -->
    <router-link to="/home" replace>首页</router-link>
    <router-link to="/about" replace>关于</router-link>
    <router-link :to="'/user/'+userId">用户</router-link>
    <router-link to="/home" tag="button" replace>首页</router-link>
    <router-link to="/about" tag="button" replace>关于</router-link>
    <button @click="homeClick">首页</button>
    <button @click="aboutClick">关于</button>
    <!-- 路由组件要展示的区域,被替换为指定的组件 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      userId: 'zhangsan'
    }
  },  
  methods: {
    homeClick() {
      // 通过代码的方式修改路由 vue-router
      // push => pushState
      // this.$router.push('/home')
      this.$router.replace('/home')
      console.log('homeClick');
    },
    aboutClick() {
      // this.$router.push('/about')
      this.$router.replace('/about')
      console.log('aboutClick');
    }
  }
}
</script>
<style>
  /*.router-link-active {*/
    /*color: #f00;*/
  /*}*/

  /*linkActiveClass: 'active':只有<router-link>在匹配到时才会出现该class  */
  .active {
    color: #f00;
  }
</style>

<router-link>还有一些其他属性:

(1)to, 用于指定跳转的路径.

(2)tag: tag可以指定<router-link>之后渲染成什么组件, 比如上面的代码会被渲染成一个

  • 元素, 而不是<a>
  • (3)replace: replace不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个页面中

    (4)active-class: 当<router-link>对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class, 设置active-class可以修改默认的名称.

    • 在进行高亮显示的导航菜单或者底部tabbar时, 会使用到该类.

    • 但是通常不会修改类的属性, 会直接使用默认的router-link-active即可.

    2、this.$router 跳转

    1.this.$router.push()

    描述:跳转到不同的url,但这个方法会向history栈添加一个记录,点击后退会返回到上一个页面。

    this.$router.push('/user/' + this.userId)

    2.this.$router.replace()

    描述:同样是跳转到指定的url,但是这个方法不会向history里面添加新的记录,点击返回,会跳转到上上一个页面。上一个记录是不存在的。

    this.$router.replace('/user/' + this.userId)

    3.this.$router.go(n)

    相对于当前页面向前或向后跳转多少个页面,类似 window.history.go(n)。n可为正数可为负数。正数返回上一个页面

    3、this.$route 参数获取

    https://router.vuejs.org/zh/api/#%E8%B7%AF%E7%94%B1%E5%AF%B9%E8%B1%A1%E5%B1%9E%E6%80%A7

    $route.path
    类型: string
    字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"。
    
    $route.params
    类型: Object
    一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
    
    $route.query
    类型: Object
    一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。
    
    $route.hash
    类型: string
    当前路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。
    
    $route.fullPath
    类型: string
    完成解析后的 URL,包含查询参数和 hash 的完整路径。
    
    $route.matched
    类型: Array<RouteRecord>
    一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)。
    当 URL 为 /foo/bar,$route.matched 将会是一个包含从上到下的所有对象 (副本)。
    
    $route.name
    当前路由的名称,如果有的话。(查看命名路由)
    
    $route.redirectedFrom
    如果存在重定向,即为重定向来源的路由的名字。(参阅重定向和别名)

    传递参数主要有两种类型: params和query

    params的类型

    配置路由格式: /router/:id

    传递的方式: 在path后面跟上对应的值

    传递后形成的路径: /router/123, /router/abc

    query的类型

    配置路由格式: /router, 也就是普通配置

    传递的方式: 对象中使用query的key作为传递方式

    传递后形成的路径: /router?id=123, /router?id=abc

    (1)params的类型

    localhost/user/zhangsan

    router/index.js

    {
        // 动态路由,路径属性,id
        path: '/user/:userId',
            component: User,
                meta: {
                    title: '用户'
                },
    },

    传递参数

    <template>
      <div id="app">
        <router-link :to="'/user/'+userId">用户</router-link>
        <button @click="userClick">用户</button>  
        <h2>{{$route.params.id}}</h2>  
      </div>
    </template>
    
    <script>
    export default {
      name: 'App',
      data() {
        return {
          userId: 'zhangsan'     
        }
      },
      methods: {
        userClick() {
          this.$router.push('/user/' + this.userId)
        }
      }
    }
    </script>

    获取活跃(当前)路由对象参数

    <h2>{{$route.params.userId}}</h2>
    
    <script>
    // vue方法中获取参数方式    
    this.$route.params.userId
    </script>

    (2)query的类型

    该方式属性通过?拼接在路径后面

    localhost:8080/profile?name=user&age=18&height=1.88

    传递属性

    :to后面为一个对象,path属性为路由,query属性为参数

    <router-link :to="{path: '/profile', query: {name: 'user', age: 18, height: 1.88}}">
    <!--==============或============= -->    
    <button @click="profileClick">档案</button>  
    
    <script>
    export default {
      name: 'App',
      methods: {
        profileClick() {
          this.$router.push({
            path: '/profile',
            query: {
              name: 'kobe',
              age: 19,
              height: 1.87
            }
          })
        }
      }
    }
    </script>  

    接收属性

    <template>
      <div>
        <h2>我是Profile组件</h2>
        <h2>{{$route.query.name}}</h2>
        <h2>{{$route.query.age}}</h2>
        <h2>{{$route.query.height}}</h2>
      </div>
    </template>

    4、路由懒加载

    更改组件导入方式

    方式一: 结合Vue的异步组件和Webpack的代码分析.

    const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};

    方式二: AMD写法

    const About = resolve => require(['../components/About.vue'], resolve);

    方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.(推荐)

    const Home = () => import('../components/Home.vue');

    5、嵌套路由

    router/index.js

    // 配置路由相关的信息
    import VueRouter from 'vue-router'
    import Vue from 'vue'
    
    // import Home from '../components/Home'
    // import About from '../components/About'
    // import User from '../components/User'
    
    // 懒加载引入组件
    const Home = () => import('../components/Home')
    const HomeNews = () => import('../components/HomeNews')
    const HomeMessage = () => import('../components/HomeMessage')
    
    // 1.通过Vue.use(插件), 安装插件
    Vue.use(VueRouter)
    
    // 2.创建VueRouter对象
    const routes = [
      {
        path: '',
        // redirect重定向
        redirect: '/home'
      },
      {
        path: '/home',
        component: Home,
        meta: {
          title: '首页'
        },
        // 子组件  ==============================
        children: [
          {
            path: '',
            redirect: 'news'
          },
          {
            path: 'news',
            component: HomeNews
          },
          {
            path: 'message',
            component: HomeMessage
          }
        ]
        // 子组件end:==============================  
      }
    ]
    const router = new VueRouter({
      // 配置路由和组件之间的应用关系
      routes,
      mode: 'history',
      linkActiveClass: 'active'
    })
    
    // 前置守卫(guard)
    //导航钩子的三个参数解析:
    //to: 即将要进入的目标的路由对象.
    //from: 当前导航即将要离开的路由对象.
    //next: 调用该方法后, 才能进入下一个钩子.
    router.beforeEach((to, from, next) => {
      // 从from跳转到to,用于修改网页标题
      document.title = to.matched[0].meta.title
      next()
    })
    
    // 后置钩子(hook)
    router.afterEach((to, from) => {
    
    })
    
    // 3.将router对象传入到Vue实例
    export default router

    components/Home.vue

        <router-link to="/home/news">新闻</router-link>
        <router-link to="/home/message">消息</router-link>
    
        <router-view></router-view>

    九、keep-alive

    1、基本介绍

    keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染(频繁创建销毁)。

    它们有两个非常重要的属性:

    • include - 字符串或正则表达,只有匹配的组件会被缓存

    • exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存

    router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:

    <keep-alive exclude="Profile,User">
        <router-view/>
    </keep-alive>

    2、动态配置页面是否缓存

    const routers = [
        {
            path: '/logistics/orderList',
            name: 'logisticsOrderList',
            component(resolve) {
                require.ensure(['@/views/logistics/orderList.vue'], () => {
                    resolve(require('@/views/logistics/orderList.vue'))
                })
            },
            meta: {
                title: '承运订单',
                requiresAuth: true,
                requiresPass: true,
                keepAlive: true,
                isBack: false
            }
        }
    ]

    如果路由页面配置了 keepAlive: true 则对页面进行缓存

    isBack: false 是标识是新创建的页面还是从其他页面返回的

    <template>
      <div id="app">
        <keep-alive>
          <router-view v-if="$route.meta.keepAlive"></router-view>
        </keep-alive>
        <router-view v-if="!$route.meta.keepAlive"></router-view>
      </div>
    </template>

    在设置keep-alive缓存的组件中,首次进入组件,会一次调用组件的钩子函数:created –> mounted –>activated 再次进入时,只触发activated钩子函数

    在使用了keepAlive之后的页面,生命周期会发生一些变化,比如create和mounted这种页面初始化相关的钩子只会执行一次,因为页面的状态已经不会自动改变了,同时有两个额外的生命周期钩子会生效,分别是:

    activated() {
        // 页面重回
    },
    deactivated() {
        // 页面离开
    },

    页面中在 activated 生命周期中判断 isBack 来决定是否需要重新加载数据

    activated: function () {
        let that = this;
        if (!this.$route.meta.isBack) {
          that.reset();
          // that.quantityUnit = this.dictMap['quantityUnit'];
          that.uploadurl = that.dyaxios.uploadUrl;
          that.countStatus();
          // that.getPagelist();
          that.reload()
        }
        this.$route.meta.isBack = false;
      },

    3、删除缓存

    跳转选择地址页面再回到填单页时,显示的填单页是上一次的缓存,这样就导致显示不正确了。

    keep-alive并未提供销毁包含组件的方法,但我们可以通过比较hack的方式去实现。

    function removeKeepAliveCache () {    
        if (this.$vnode && this.$vnode.data.keepAlive && this.$vnode.parent) {        
            const tag = this.$vnode.tag;        
            let caches = this.$vnode.parent.componentInstance.cache;        
            let keys = this.$vnode.parent.componentInstance.keys;        
            for (let [key, cache] of Object.entries(caches)) {            
                if (cache.tag === tag) {                
                    if (keys.length > 0 && keys.includes(key)) {                    
                        keys.splice(keys.indexOf(key), 1);                
                    }                
                    delete caches[key];            
                }        
            }    
        }    
        this.$destroy();
    };

    然后,在组件里的路由守卫调用removeKeepAliveCache,同时也不需要去修改from.meta.keepAlive

    beforeRouteLeave(to, from, next) {    
        if (to.name === 'selectAddr') {        
            // from.meta.keepAlive = true;    
        } else {        
            // from.meta.keepAlive = false;        
            removeKeepAliveCache.call(this);    
        }    
        next();
    },

    至此,就实现了我们开头所说的需求,填单页跳转到选择地址页,再返回到填单页时,填单页信息不变。再次重新进入填单页时,页面会刷新。

    十、Promise

    Promise是异步编程的一种解决方案。

    但是,当网络请求非常复杂时,就会出现回调地狱。也就是出现多层网络请求的嵌套。

    1、基本使用

    1、new Promise 很明显是创建一个Promise对象

    2、参数为((resolve, reject) => {}) 是固定的,resolvereject它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个。

    3、如果是成功的,调用resolve(messsage),这个时候,我们后续的then会被回调。

    如果是失败的,调用reject(error),这个时候,我们后续的catch会被回调。

    new Promise((resolve, reject) => {
        setTimeout(() => {
            // 成功的时候调用resolve
            resolve('Hello World')
    
            // 失败的时候调用reject
            //reject('error message')
    
            // resolve, reject之后的代码依旧会执行
            setTimeout(()=>{
                console.log("end")
            },1000)
    
    
        }, 1000)
    }).then((data) => {    // data为成功的时候调用resolve的参数
        console.log(data);
    }).catch((err) => {
        console.log(err);
    })

    网页打开等待一秒,输出Hello World,在等待一秒,输出end

    Hello World
    end

    写法二

    new Promise((resolve, reject) => {
        setTimeout(() => {
            //resolve('Hello Vuejs')
            reject('error message')
        }, 1000)
    }).then(data => {
        console.log(data);
    }, err => {
        console.log(err);
    })

    2、三种状态

    首先, 当我们开发中有异步操作时, 就可以给异步操作包装一个Promise

    异步操作之后会有三种状态

    我们一起来看一下这三种状态:

    pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。

    fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()

    reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()

    3、链式调用

    无论是then还是catch都可以返回一个Promise对象。所以,可以进行链式调用的:

    直接通过Promise包装了一下新的数据,将Promise对象返回了

    Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数

    Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数

    // 参数 -> 函数(resolve, reject)
    // resolve, reject本身它们又是函数
    // 链式编程
    new Promise((resolve, reject) => {
    
        // 第一次网络请求的代码
        setTimeout(() => {
            resolve()
        }, 1000)
    
    }).then(() => {
        // 第一次拿到结果的处理代码
        console.log('Hello World');
    
        return new Promise((resolve, reject) => {
    
            // 第二次网络请求的代码
            setTimeout(() => {
                resolve()
            }, 1000)
        })
    }).then(() => {
    
        // 第二次处理的代码
        console.log('Hello Vuejs');
    
        return new Promise((resolve, reject) => {
    
            // 第三次网络请求的代码
            setTimeout(() => {
                resolve()
            })
        })
    }).then(() => {
    
        // 第三次处理的代码
        console.log('Hello Python');
    })

    如果我们希望数据直接包装成Promise.resolve,那么在then中可以直接返回数据

    注意下面的代码中,我讲return Promise.resovle(data)改成了return data

    结果依然是一样的

    // new Promise(resolve => resolve(结果))简写
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('aaa')
        }, 1000)
    }).then(res => {
        // 1.自己处理10行代码
        console.log(res, '第一层的10行处理代码');
    
        // 2.对结果进行第一次处理
        return Promise.resolve(res + '111')
    }).then(res => {
        console.log(res, '第二层的10行处理代码');
    
        return Promise.resolve(res + '222')
    }).then(res => {
        console.log(res, '第三层的10行处理代码');
    })
    
    // 省略掉Promise.resolve
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('aaa')
        }, 1000)
    }).then(res => {
        // 1.自己处理10行代码
        console.log(res, '第一层的10行处理代码');
    
        // 2.对结果进行第一次处理
        return res + '111'
    }).then(res => {
        console.log(res, '第二层的10行处理代码');
    
        return res + '222'
    }).then(res => {
        console.log(res, '第三层的10行处理代码');
    })
    
    
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('aaa')
        }, 1000)
    }).then(res => {
        // 1.自己处理10行代码
        console.log(res, '第一层的10行处理代码');
    
        // 2.对结果进行第一次处理
        // return Promise.reject('error message')
        throw 'error message'
    }).then(res => {
        console.log(res, '第二层的10行处理代码');
    
        return Promise.resolve(res + '222')
    }).then(res => {
        console.log(res, '第三层的10行处理代码');
    }).catch(err => {
        console.log(err);
    })

    4、all() 方法

    同时发送多个请求,并获取响应

    Promise.all([promis对象1,promise对象2...]).then((所有promis中resolve方法的参数数组)=>{})
    Promise.all([
        new Promise((resolve, reject) => {
            $.ajax({
                url: 'url1',
                success: function (data) {
                    resolve(data)
                }
            })
        }),
        new Promise((resolve, reject) => {
            $.ajax({
                url: 'url2',
                success: function (data) {
                    resolve(data)
                }
            })
        })
    
    ]).then(results => {
        console.log(results);
        console.log(results[0]);    //第一个resolve(data)中的data
        console.log(results[1]);    //第二个resolve(data)中的data
    })

    十一、axios

    安装命令:npm install axios --save

    引入:import axios from 'axios'

    1、配置项

    • axios(config) 默认为get

    • axios.request(config)

    • axios.get(url[, config])

    • axios.delete(url[, config])

    • axios.head(url[, config])

    • axios.post(url[, data[, config]])

    • axios.put(url[, data[, config]])

    • axios.patch(url[, data[, config]])

    config 配置项

    #请求地址
    url: '/user',
    #请求类型
    method: 'get',
    #请根路径,baseURL会添加到url前(url是绝对地址除外)。
    baseURL: 'http://www.mt.com/api',
    #请求前的数据处理,只适用于以下请求方式:put/post/patch
    transformRequest:[function(data){}],
    #请求后的数据处理,选项允许我们在数据传送到then/catch方法之前对数据进行改动
    transformResponse: [function(data){}],
    #自定义的请求头
    headers:{'x-Requested-With':'XMLHttpRequest'},
    #URL查询对象
    params:{ id: 12 },
    #查询对象序列化函数
    paramsSerializer: function(params){ }
    #request body
    data: { key: 'aa'},
    #超时设置ms
    timeout: 1000,
    #跨域是否带Token
    withCredentials: false,
    #自定义请求处理
    adapter: function(resolve, reject, config){},
    #身份验证信息
    auth: { uname: '', pwd: '12'},
    #响应的数据格式 json / blob /document /arraybuffer / text / stream
    responseType: 'json',
    #是否是跨域请求、默认是default
    withCredentials:
    #上传进度事件
    onUploadProgress
    #下载进度的事件
    onDownloadProgress
    #相应内容的最大值
    maxContentLength

    2、基本使用

    axios 返回的是一个promise对象,所以可以直接调用 .then 方法

    GET

    // 方法一
    axios({
        url: 'http://123.207.32.32:8000/home/data',
        // 默认就是get,可以省略
        method: 'get',    
        // 专门针对get请求的参数拼接
        params: {
            type: 'pop',
            page: 1
        }
    }).then(res => {
        console.log(res);
    }).catch(err => {
    })
    
    
    // 方法二
    axios.get('http://123.207.32.32:8000/home/multidata',{
      params:{
        name: "李四"
      }
    }).then(res =>{
    
    }).catch(err => {
    
    })

    POST

    //方法一
    axios({
        url: 'http://123.207.32.32:8000/home/multidata',
        method: 'post',
        data: {
            name: '李四'
        }
    }).then(res=>{
    
    }).catch(err=>{
    
    })
    
    //方法二
    axios.post('http://123.207.32.32:8000/home/multidata',{
        data:{
            name: '李四'
        }
    }).then(res=>{
    
    }).catch(err=>{
    
    })

    3、发送并发请求

    需要多个请求都拿到结果时在执行剩下的代码

    axios.all([axios({
        url: 'http://123.207.32.32:8000/home/multidata'
    }), axios({
        url: 'http://123.207.32.32:8000/home/data',
        params: {
            type: 'sell',
            page: 5
        }
    })]).then(results => {
        console.log(results);
        console.log(results[0]);    // 第一个请求的响应结果
        console.log(results[1]);    // 第二个请求的响应结果
    })

    4、全局配置

    axios.defaults.配置项

    axios.defaults.baseURL = 'http://222.111.33.33:8000'
    axios.defaults.timeout = 10000

    一般开发不会使用全局配置,因为如果是微服务则各个模块可能配置不同,此时可以采用axios实例

    5、axios实例

    为什么要创建axios的实例呢?

    • 当我们从axios模块中导入对象时, 使用的实例是默认的实例.

    • 当给该实例设置一些默认配置时, 这些配置就被固定下来了.

    • 但是后续开发中, 某些配置可能会不太一样.

    • 比如某些请求需要使用特定的baseURL或者timeout或者content-Type等.

    • 这个时候, 我们就可以创建新的实例, 并且传入属于该实例的配置信息.

    const instance1 = axios.create({
        baseURL: 'http://123.207.32.32:8000',
        timeout: 5000
    })
    
    instance1({
        url: '/home/multidata'
    }).then(res => {
        console.log(res);
    })

    6、axios封装/拦截器

    (1)封装

    network/request.js

    import axios from '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)
    }

    (2)发送请求

    import {request} from "./network/request";
    
    request({
        url: '/home/multidata'
    }).then(res => {
        console.log(res);
    }).catch(err => {
        console.log(err);
    })

    十二、vuex

    1、简介

    简单说就是响应式的状态的集中式存储管理,就和react的redux差不多。

    是通过单例模式实现的。

    image-20211025210305230

    state :状态,单一状态树,相当于全局的 data

    actions :动作,用于定义异步方法,一般是请求后端接口

    Mutations: 变化,更改 Vuex 的 store 中的状态的唯一方法,用于定义同步方法

    文件结构

    ├── index.html
    ├── main.js
    ├── api
    │   └── ... # 抽取出API请求
    ├── components
    │   ├── App.vue
    │   └── ...
    └── store
        ├── index.js          # 我们组装模块并导出 store 的地方
        ├── actions.js        # 根级别的 action
        ├── mutations.js      # 根级别的 mutation
        └── modules
            ├── cart.js       # 购物车模块
            └── products.js   # 产品模块

    2、基本使用步骤

    (1)安装

    vue 2

    npm install --save vuex@3.6.2

    vue 3

    npm install --save vuex@4.0.0

    (2)store.js

    创建一个文件夹store,并且在其中创建一个index.js文件

    import Vue from 'vue'
    import Vuex from 'vuex'
    // 1.安装插件
    Vue.use(Vuex)
    
    // 2.创建对象 new Vuex.Store
    const store = new Vuex.Store({
        // 这5个对象一般都是固定的
        state: { //保存状态
            counter: 1000
        },
        mutations: { // 方法 修改state唯一途径 同步操作
            increment(state) { // 默认就有个state参数,不用通过this.state
                state.counter++
            },
            decrement(state) {
                state.counter--
            }
        },
        actions: { // 如果有异步操作在这里写 比如网络请求
    
        },
        getters: {    // 计算属性
    
        },
        modules: {
    
        }
    })
    // 3.导出store对象
    export default store

    (3)main.js

    让所有的Vue组件都可以使用这个store对象

    • 来到main.js文件,导入store对象,并且放在new Vue中
    • 这样,在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了
    import Vue from 'vue'
    import App from './App'
    import store from './store'
    
    Vue.config.productionTip = false
    
    // 其他组件就能通过 $store 获取到store
    new Vue({
      el: '#app',
      store,
      render: h => h(App)
    })

    (4)普通组件使用

    • 通过 this.$store.state.属性 的方式来访问状态
    • 通过 this.$store.commit('mutation中方法') 来修改状态
    <template>
      <div id="app">
        <h2>-----App.vue内容-----</h2>
        <!-- 通过this.$store.state.属性的方式来访问状态 -->
        <h2>{{ $store.state.counter }}</h2>
        <!-- 官方不建议这么改,因为工具devtools监听不到 -->
        <!-- <button @click="$store.state.counter++">+</button>
        <button @click="$store.state.counter--">-</button> -->
        <button @click="addition">+</button>
        <button @click="subtraction">-</button>
        <hr />
        <h2>-----HelloVuex内容-----</h2>
        <HelloVuex></HelloVuex>
      </div>
    </template>
    
    <script>
    import HelloVuex from "./components/HelloVuex.vue";
    
    export default {
      name: "App",
      components: {
        HelloVuex,
      },
      data() {
        return {};
      },
      methods: {
        // 在这里提交方法对应的mutation 的方法名
        addition() {
          // 通过this.$store.commit('mutation中方法')来修改状态
          this.$store.commit("increment");
        },
        subtraction() {
          this.$store.commit("decrement");
        },
      },
    };
    </script>

    3、state访问与修改

    定义

    state: { //保存状态
        counter: 1000
    },

    访问

    $store.state.counter
    {{ $store.state.counter }}

    修改

    先在store.js定义修改方法

    mutations: { // 方法 修改state唯一途径 同步操作
        increment(state) { // 默认就有个state参数,不用通过this.state
            state.counter++
        },
        decrement(state) {
            state.counter--
        },
        // 传递参数的写法,传多个参数用对象    
        incrementNum (state, Num) {
            state.count += Num
        }    
    },

    在普通组件中修改state

    this.$store.commit("increment");
    this.$store.commit('incrementNum', 10);

    4、计算属性getters

    其他组件需要的状态必须是 state 中的状态经过变化后的值,此时就可以用计算属性

    定义计算属性 store.js

    getters的方法可以接受2个参数

    const store = new Vuex.Store({
      state: { //保存状态
        counter: 1000,
      },
      getters: { // 可以认为是 store 的计算属性
        // getters里面的方法 也会有state参数
        powerCounter(state) {
          return state.counter * state.counter
        },
    
    
        // filter(回调函数(当前元素的值))
        more20stu(state) {
          return state.students.filter(s => s.age > 20)
        },
        // 可以接受两个参数
        more20stuLength(state, getters) { // Getters 也可以接受其他 getters 作为第二个参数:
          //  return state.students.filter(s => s.age > 20).length
          return getters.more20stu.length
        },
    
      }
    })

    普通组件使用计算属性

    $store.getters.powerCounter
    <h2>{{ $store.getters.powerCounter }}</h2>

    5、动态计算属性

    当需要getters根据具体页面的传参和state,共同计算得出值时,可以利用函数科里化。

    getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.

    定义

    // .. 
    getter:{
        // 找出年龄大于参数age的学生
        moreAgeStu(state) { // getters传递参数 只能让getters本身返回另一个函数.
          // return function (age) {
          //   return state.students.filter(s => s.age > age)
          // }
          return age => {
            return state.students.filter(s => s.age > age)
          }
      }
    } 
    // ...

    使用

    <h2>{{ $store.getters.moreAgeStu(12) }}</h2>

    6、mutation

    • Vuex的store状态的更新唯一方式:提交Mutation
    • Mutation主要包括两部分:
      • 字符串的事件类型(type)
      • 一个回调函数(handler),该回调函数的第一个参数就是state。
    img

    在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数

    • 参数被称为是mutation的载荷(Payload)

    接收单个参数

    定义mutations

    // ...
      mutations: { // 方法 修改state唯一途径 同步操作
        // mutations传递参数
        // 单个参数
         incrementCount(state, count) {
            console.log(count);
            state.counter +=count
         },
      },
    
    // ...

    调用并传参

    this.$store.commit('incrementCount', count) // 单个参数

    接收多个参数

    但是如果参数不是一个呢?

    • 比如我们有很多参数需要传递.
    • 这个时候, 我们通常会以对象的形式传递, 也就是payload是一个对象.
    • 这个时候可以再从对象中取出相关的信息.

    定义mutations

    // ...
      mutations: { // 方法 修改state唯一途径 同步操作
    
        // 参数是对象
        addStudent(state, stu) {
          state.students.push(stu)
        },
      },
    
    // ...

    调用并传多个参数

    // App.vue的methods
    addStudent() {
        // 提交对象
        const stu = { id: 114, name: "alan", age: 35 };
        this.$store.commit("addStudent", stu);
    },

    特殊的提交风格

    定义

    // ...
    mutations:{
         // 特殊的提交封装
        incrementCount(state, payload) {
          // console.log(count);
          state.counter += payload.count
        },
    }

    使用

     addCount(count) {
         // payload: 负载
         // 1.普通的提交封装 这样写的 mutations里的 incrementCount(state, count) 的count 就是count
         // this.$store.commit('incrementCount', count) // 单个参数
    
         // 2.特殊的提交封装 mutations里的 incrementCount(state, count) 的count 是一个对象 写成payload比较合适,通过payload.count取
         this.$store.commit({
             type: "incrementCount",
             count,    
         });
     },

    响应规则

    • Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.

    • 这就要求我们必须遵守一些Vuex对应的规则:

      • 提前在store中初始化好所需的属性.
      • 当给state中的对象添加新属性时, 使用下面的方式:
        • 方式一: 使用Vue.set(obj, ‘newProp’, 123)
        • 方式二: 用新对象给旧对象重新赋值
    const store = new Vuex.Store({
      state: { //保存状态
        // state里面都属性初始化后 每个属性对应一个dep 监听属性变化 dep观察者模式
        info: {
              name: 'kobe', // Dep -> [Watcher]
              age: 40, // Dep -> [Watcher]
              height: 1.98 // Dep -> [Watcher]
          },
        students: ["张三","王五"],  
        },
        mutations: {
            updateInfo(state) {
              // 可以响应式
              state.info.name = 'coderwhy'
              // 修改 state.info 对象的 'address' 属性的值为 '洛杉矶'
              Vue.set(state.info, 'address', '洛杉矶')
              // 修改   state.students 数组的索引为1的值为 '赵六'
              Vue.set(state.students, 1, '赵六')  
    
              // 不能响应式
              // state.info['address'] = '洛杉矶' 
    
              // “delete+某个属性”该方式做不到响应式,该方法用于删除对象中指定属性
              delete state.info.age  
              // Vue.delete() 响应式
              Vue.delete(state.info, 'age') 
           }
        }
    })

    提取类型常量

    • 创建一个文件: mutation-types.js, 并且在其中定义我们的常量.
    • 定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称.

    mutations-type.js代码:

    export const INCREMENT = 'increment'

    store下index.js

    // import INCREMENT from './mutations-types'// 不能这样导入,只能是export default
    // export导出,导入需要加 {}
    import {
      INCREMENT
    } from './mutations-types'
    
    const store = new Vuex.Store({
      state: { ... },
      mutations: {
         // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
        [INCREMENT](state) {
          state.counter++
        },
      }
    })

    App.vue

    import HelloVuex from "./components/HelloVuex";
    
    import { INCREMENT } from "./store/mutations-types";
    
    
    export default {
      name: "App",
      methods: {
        // 在这里提交方法对应的mutation 的方法名
        addition() {
          this.$store.commit(INCREMENT);
        }
      },
    }

    Mutation同步函数

    • 通常情况下, Vuex要求我们Mutation中的方法必须是同步方法.
      • 主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.
      • 但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.

    7、action

    在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch

    同样的, 也是支持传递payload

    关于context:

    • context是什么? context是和store对象具有相同方法和属性的对象.
    • 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.
    • 但是注意, 这里它们并不是同一个对象, 为什么呢? 本文Modules具体说.

    基本使用

    定义action

    // ...
    const store = new Vuex.Store({
        mutations:{
             updateInfo(state) {
                  state.info.name = 'coderwhy'
            }
        },
        actions:{
              // 默认参数 context: 上下文 现在先理解成store
             aUpdateInfo(context, payload) {
                  setTimeout(() => {
                    context.commit('updateInfo')
                    console.log(payload.message);
                    payload.success() // 调用回调 告诉外面已经成功
                  }, 1000)
            },
        }
    
    })
    
    // ...

    调用action

    // ...
        updateInfo() {
          // this.$store.commit('updateInfo')  // 这样没有经过actions
          // 异步修改信息 actions
          // this.$store.dispatch('aUpdateInfo',"我是携带的信息");// 携带一个参数
    
          // 通知外面已经改成功了 -> commit调用之后就算成功,利用对象的方法回调
          this.$store.dispatch('aUpdateInfo', {
            message: '我是携带的信息',
            success: () => {
              console.log('里面已经完成了');
            }
          })
    
        },
    
    // ...

    返回Promise

    • 在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的resolve或reject.

    vuex

    // ...
    actions:{
         // Action返回的Promise
        aUpdateInfo(context, payload) {
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              context.commit('updateInfo');
              console.log(payload);
              resolve('1111111')
            }, 1000)
          }) // then()在App.vue里面写
        }
    }
    
    // ...

    调用

    // ...
     updateInfo() {
          // 之前写的不够优雅 回调消息与携带信息混在一起,现在用Promise封装再用resolve调用
          this.$store.dispatch("aUpdateInfo", "我是携带的信息").then((res) => {
            console.log("里面完成了提交");
            console.log(res);
          });
        },
    
    // ...

    8、modules

    Module是模块的意思, 为什么在Vuex中我们要使用模块呢?

    • Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.
    • 当应用变得非常复杂时,store对象就有可能变得相当臃肿.
    • 为了解决这个问题, Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutations、actions、getters等
    基本使用

    img

    注意action里面的context.commit()只能提交到当前模块的mutation中

    获取状态

    store.state.a    //后面不需要再加一个state了

    调用方法

    和全局的一致,无需考虑在哪个模块

    updateName() {
        this.$store.commit('updateName', 'lisi')
    },
    asyncUpdateName() {
        this.$store.dispatch('aUpdateName')
    }

      目录