Vue框架

Vue.js是什么

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

安装

Vue.js设计的初衷就包括可以被渐进式地采用。这意味着它可以根据需求以多种方式集成到一个项目中。
将Vue.js添加到项目中主要有四种方式:

  1. 在页面上以CDN包的形式导入。
  2. 下载JavaScript文件并自行托管
  3. 使用npm安装它
  4. 使用官方的CLI来构建一个项目,它为现代前端工作流程提供了功能齐备的构建设置(如:热重载、保存时的提示等等)

CDN

<script src="https://unpkg.com/vue@next"> </script>

下载并自托管

如果你想避免使用构建工具,但又无法在生产环境使用CDN,那么你可以下载相关.js文件并自行托管在你的服务器上。然后你可以通过<script>标签引入,与CDN的方法类似。
这些文件可以在unpkg或者jsDelivr这些CDN上浏览和下载。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./js/vue.js"></script>
</head>
<body>
    <div id="root">
        <p>{{num}}</p>
        <p>{{uname}}</p>
    </div>

    <script>
        const conster = {
            data : function(){
                return {
                    num : 0,
                    uname:"张三",
                }
            }
        }

        Vue.createApp(conster).mount("#root")
    </script>
</body>
</html>

vite

Vite是一个Web开发构建工具,由于其原生ES模板导入方式,可以实现闪现般的冷服务器启动。通过在终端中运行一下命令,可以使用Vite快速构建Vue项目

创建项目

npm
npm init vite@latest [project name] -- --template vue

cd project name
npm install
npm run dev

声明式渲染

Vue.js的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进DOM的系统

<script>
   export default{
     data(){
      return{
        num : 1 ,
        uname: "张三"
      }
     }
   }
</script>

<template>
  <div>
    <p>{{ num }}</p>
    <p>{{ uname }}</p>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

根组件

传递给createApp的选项用于配置根组件。当我们挂载应用时,该组件被用作渲染的起点。
一个应用需要被挂载到一个DOM元素中。
与大多数应用方法不同的是,mount不返回应用本身。相反,它返回的是根组件实例。
虽然没有完全遵循 MVVM模型,但是Vue的设计也受到了它的启发。因此在文档中经常会使用vm这个变量名表示组件实例。

还有其他各种的组件选项,可以将用户定义的property添加到组件实例中,例如:methods、props、computed、inject 和 setup。我们将在后面的指南中深入讨论它们。组件实例的所有property,无论如何定义,都可以在组件模板中访问。
Vue还通过组件实例暴露了一些内置property,如$attrs和$emit。这些property都有一个$前缀,以避免与用户定义的property名冲突。

模板语法

Vue.js使用了基于HTML的模板语法,允许开发者声明式地将DOM绑定至顶层组件实例的数据。所有Vue.js的模板都是合法的HTML,所以能被遵循规范的浏览器和HTML解析器解析。
在底层的实现上,Vue将模板编译成虚拟DOM渲染函数。结合响应性系统,Vue能够智能地计算出最少需要重新渲染多少组件,并把DOM操作次数减到最少。
如果你熟悉虚拟DOM并且偏爱JavaScript的原始力量,你也可以不用模板,直接写渲染(reader)函数 ,使用可选的JSX语法。

插值

文本

数据绑定最常见的形式就是使用“Mustache”(双大括号)语法的文本插值

<span> Message:{{msg}}</span>

Mustache标签将会被替代为对应组件实例中,msg Property的值。无论何时,绑定的组件实例上msg property发生了改变,插值处的内容都会更新。
通过使用** v-once指令**,你也能执行一次性的插值,当数据发生改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其他数据绑定。

<script>
   export default{
     data(){
      return{
        num : 1 ,
        uname: "张三"
      };
     },
     methods:{
      changeuname(){
        this.uname = "李四";
      },
     },
   }
</script>

<template>
  <div>
    <p>{{ num }}</p>
    <p>{{ uname }}</p>

    <p v-once>{{ uname }}</p>
    <button @click="changeuname">改变名字</button>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

原始HTML

双大括号会将数据解释为普通文本,而非HTML代码。为了输出真正的HTML,你需要使用v-html指令

<script>
   export default{
     data(){
      return{
        num : 1 ,
        uname: "张三",
        msg:"<h2>标题</h2>"
      };
     },
     methods:{
      changeuname(){
        this.uname = "李四";
      },
     },
   }
</script>

<template>
  <div>
    <p>{{ num }}</p>
    <p>{{ uname }}</p>
    <p>{{ msg }}</p>
    <p v-html="msg"></p>

    <p v-once>{{ uname }}</p>
    <button @click="changeuname">改变名字</button>
  </div>
</template>

<style scoped>
</style>

注意:
你不能使用v-html来复合局部模板,因为Vue不是基于字符串的模板引擎。反之,对于用户界面(UI),组件更适合作为可重用和可组合的基本单位

TIP

在你的站点上动态渲染任意的HTML是非常危险的,因为它很容易导致XSS攻击。请只对可信任内容使用HTML插值,绝不要将用户提供的内容作为插值。

Attribute

Mustache语法不能在HTML attribute中使用,然而,可以使用v-bind指令:

<div v-bind:id= "d1" ></div>

如果绑定的值是null或undefined,那么该attribute将不会被包含在渲染的元素上
对于布尔attribute(它们只要存在就意味着值为true),v-bind工作起来略有不同。

简写

因为v-bind非常常用,我们提供了特定的简写语法:

<div :id="d1"></div>

使用JavaScript表达式

迄今为止,在我们的模板中,我们一直都只绑定简单的property键值。但实际上,对于所有的数据绑定,Vue.js都提供了完全的JavaScript表达式支持

{{number+1}}
{{ok?'yes':'no}}
{{message.split('').reverse().join('')}}
<div v-bind:id="list-' + id"></div>

这些表达式会在当前活动实例的数据作用域下作为JavaScript被解析。有个限制就是,每个绑定都只能包含单个表达式。

{{var a = 1}}  //这是语句,不是表达式
{{if(ok){return message}}}  //流程控制也不会生效,请使用三元表达式

参数

一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如:v-bind指令可以用于响应式的更新HTML attribute:

<a v-bind:href:"url">...<a>

在这里href是参数,告知v-bind指令将该元素的href attribute与表达式url的值绑定
另一个例子是v-on指令,它用于监听DOM时间

<a v-on:click = "dosomething">...</a>

这里的参数是监听的事件名。

v-on 简写

v-on 也非常的常用,因此我们可以简写为:

<a @click = "dosomething">...</a>

动态参数

也可以在指令参数中使用JavaScript表达式,方法是用方括号括起来:

<a v-bind>:[attributeName] = "url">...</a>  //参数表达式的写法存在一些约束

这里的attributeName 会被作为一个JavaScript表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的组件实例有一个data property attributeName,其值为“href”,那么这个绑定将等价于v-bind:href.
同样的,你可以使用动态参数为一个动态的事件名绑定处理函数:

<a v-on:[eventName] = "doSomething"> ... </a>

在这个示例中,当eventName 的值为“focus”时,v-on:[eventName] 将等价于v-on:focus

指令

指令(Directives)是带有v-前缀的特殊attribute。指令attribute的值预期是单个 JavaScript表达式(v-for 和 v-on是例外情况)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM。

<p v-if="seen">现在你看到我了</p>

这里,v-if指令将根据表达式seen的值的真假来插入/移除<p>元素

Data Property和方法

Data Property

组件的data选项是一个函数。Vue 会在创建新组件的实例的过程中调用此函数。它应该返回一个对象,然后Vue会通过响应性系统将其包裹起来,并以$data的形式存储在组件实例中。为方便起见,该对象的任何顶级property也会直接通过组件实例暴露出来。
这些实例property仅在实例首次创建时被添加,所以你需要确保它们都在data函数返回的对象中。必要时,要对尚未提供所需值的property使用null,undefined或其他占位的值
直接将不包含在data中的新property添加到组件实例是可行的。但由于该property不在背后的响应式$data对象内,所以Vue的响应性系统不会自动跟踪它。

Vue使用$前缀通过组件实例暴露自己的内置API。它还为内部property保留_前缀。你应该避免使用这两个字符 开头的顶级data property名称

方法

我们用methods选项向组件实例添加方法,它应该是一个包含所需方法的对象。
Vue自动为methods绑定this,以便于它始终指向组件实例。这将确保方法在用事件监听或回调时保持正确的this指向。在定义methods时应避免使用箭头函数,因为这会组织Vue绑定恰当的this指向。
这些methods和组件实例的其他所有property一样可以在组件的模板中被访问。在模板中,它们通常被当做事件监听使用。
也可以直接从模板调用方法。通常换做计算属性会更好。但是,在计算属性不可行的情况下,使用方法可能会很有用。你可以在模板支持JavaScript表达式的任何地方调用方法:

<span :title = "toTitleDate(data)"
{{formatDate(data)}}
</span>

如果toTitleDate或formatDate访问了任何响应式数据,则将其作为渲染依赖项进行跟踪,就像直接在模板中使用过一样。
从模板调用的方法不应该有任何副作用,比如更改数据或触发一步进程。如果你想这么做,应该使用生命周期钩子来替换。

计算属性和侦听器

计算属性

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。
对于任何包含响应式数据的复杂逻辑,都应该使用计算属性

计算属性的getter函数没有副作用,它更易于测试和理解。

<script>
   export default{
     data(){
      return{
        message:"HelloWorld!",
        age : 0
      };
     },
     computed: {
      reversemsg: function(){
        return this.message.split("").reverse().join("");
      },
         
     },
     methods:{
      reverseMessage:function(){
        return this.message.split("").reverse().join("");
      }
     }
   }
</script>

<template>
  <div>
    <p>{{ message }}</p>
    //第一种:js表达式
    <p>{{ message.split("").reverse().join("") }}</p>
    <p>{{ message.split("").reverse().join("") }}</p>
    <p>{{ message.split("").reverse().join("") }}</p>
    //第二种:使用计算属性
    <p>{{ reversemsg }}</p>
    //第三种:使用methods中的方法
    <p>{{ reverseMessage() }}</p>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
#d1{
  color: red;
}
#d2{
  color: blue;
}
</style>

计算属性的Setter

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

<script>
   export default{
     data(){
      return{
        message:"HelloWorld!",
        age : 0
      };
     },
     computed: {
      reversemsg: {
        set:function(newValue){
          console.log(newValue);
        },
        get:function(){
        return this.message.split("").reverse().join("");
      },
     },
         
     },
     methods:{
      reverseMessage:function(){
        return this.message.split("").reverse().join("");
      }
     }
   }
</script>

<template>
  <div>
    <p>{{ message }}</p>
    <p>{{ message.split("").reverse().join("") }}</p>
    <p>{{ message.split("").reverse().join("") }}</p>
    <p>{{ message.split("").reverse().join("") }}</p>
    <p>{{ reversemsg }}</p>
    <p>{{ reverseMessage() }}</p>
    <button @click="reversemsg = '你好'">改变属性</button>
    <button @click="message = '你好'">改变值</button>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
#d1{
  color: red;
}
#d2{
  color: blue;
}
</style>

侦听器

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

<script>
   export default{
     data(){
      return{
        message:"HelloWorld!",
        age : 0
      };
     },
     methods:{
     },

     watch:{
      message:function(newValue , oldValue){
        console.log(newValue);
        console.log(oldValue);
      }
     }
   }
</script>

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="message = '你好'">改变值</button>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
#d1{
  color: red;
}
#d2{
  color: blue;
}
</style>

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

计算属性 vs 侦听器

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

深度监听

<script>
   export default{
     data(){
      return{
        message:"HelloWorld",
        age : 0,
        usr:{
          name:"张三",
        }
      };
     },
     methods:{
     },

     watch:{
      // message:function(newValue , oldValue){
      //   console.log(newValue);
      //   console.log(oldValue);
      //   if(newValue.length<5 || newValue.length > 10)
      //     console.log("输入框中的字符在5-10之间");
      // }

      message:{
        immediate:true,
        handler:function(newValue){
          console.log(newValue);
          if(newValue.length<5 || newValue.length > 10)
          console.log("输入框中的字符在5-10之间");
        }
      },

      // usr:{
      //   handler:function(newValue){
      //     console.log(newValue);
      //   },
      //   deep:true,
      // }

      "usr.name":{
        handler:function(newValue){
          console.log(newValue);
        },
        deep:true,
      }
     }
   }
</script>

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="message = '你好'">改变值</button>
    <input type="text" v-model = "message">

    <p>{{ usr.name }}</p>
    <button @click="usr.name = '李四'">改变名字</button>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
#d1{
  color: red;
}
#d2{
  color: blue;
}
</style>

监听不到对象的属性变化,需要用到深度监听deep
deep:true, 表示是否深度监听,侦听器会一层层的向下遍历,给对象每个属性都加上侦听器
immediate:true, 表示初始化的时候调用函数。

Class与Style绑定

操作元素的Class列表和内联样式是数据绑定的一个常见需求。因为它们都是attribute,所以我们可以用v-bind处理它们:只需要通过表达式计算出字符串结果即可。不过字符串拼接麻烦且易错。因此,在将v-bind用于class和style时,Vue.js做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。

绑定HTML Class

对象语法

我们可以传给:class(v-bind:class的简写)一个对象,以动态地切换class:

<div :class = {"active:isActive}"></div>

上面的语法表示active这个class存在与否将取决于data property isActive 的truthiness
你可以在对象中传入更多字段来动态切换多个class。此外,:class指令也可以与普通的class attribute共存。

当isActive 或者 Error变化时,class列表将相应的更新。

数组语法

我们可以把一个数组传递给:class,以应用一个class列表:

<script>
   export default{
     data(){
      return{
        message:"HelloWorld",
        isActive:true,
        error:10,
        classObj:{
          active:true ,
          helloworld:true ,
        },
        activeClass:"active",
      };
     },
     computed:{
      chassObjcom:function(){
        return{
          active:this.isActive && !this.error,
          helloworld:this.error
        }
      }
     }
   }
</script>

<template>
  <div>
    <!-- 第一种,放置字符串 -->
    <p class="active">Hello</p>
    <!-- 第二种,放置对象(常用) -->
    <!-- p :class="{类名:boolean}" >hello</p>-->
    <p :class="{active:isActive}">Hello</p>
    <p :class="{active:isActive,helloworld:isActive}">Hello</p>

    <!-- 和普通的类同事存在,不会冲突 -->
    <p :class="{active:isActive}" class="helloworld">Hello</p>

    <button @click="isActive=!isActive">改变属性</button>
    
    <p :class="classObj">Hello</p>
    <hr/>

    <!-- 使用computed -->
    <p :class="chassObjcom">Hello</p>
    <!-- 使用数组语法(不常用) -->
    <p :class="[message,activeClass]">Hello</p>
    <p :class="[message,{active:isActive}]">hello</p>
  </div>
</template>

<style>
.active{
  font-size: 50px;
  color: blue;
}

.helloworld{
  background-color: pink;
}
body {
  display: block;
}
#app {
  text-align: left;
}
</style>

绑定内联样式

对象语法

v-bind:style 的对象语法十分直观——看着非常像CSS,但其实是一个JavaScript对象。CSS property名可以用驼峰式(camelCase)或短横线分隔(kebab-case,记得用引号括起来)来命名:

<div v-bind:style="{color:activeColor,fontSize:fontSize + 'px'}"></div>

data:{
   activeColor:'red',
   fontSize:30
   }

数组语法

v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

<div v-bind:style="[baseStyles,overridingStyle]"></div>

自动添加前缀

v-bind:style使用需要添加浏览器引擎前缀的CSS property时,如:transform,Vue.js会自动侦测并添加相应的前缀。

<script>
   export default{
     data(){
      return{
        activeColor:'red',
        fontsize:'50px',
        bgcolor:'pink',
        styleObj:{
          color:'red',
          fontSize: '50px',
          backgroundColor:'pink'
        },
        styleOj:[
        {
          color:'red',
          fontSize: '50px',
          backgroundColor:'pink'
        },
          {border:'5px solid blue'}
        ]
      };
     },
   }
</script>

<template>
  <div>
    <!-- 第一种,放置字符串 -->
    <p style="color:red">Hello</p>
    <!-- 第二种,放置对象 -->
    <!-- p:style="key(css属性名):value(属性值,来自于data中的属性)}" -->
    <p :style="{color:activeColor,fontSize: fontsize,backgroundColor:bgcolor}">hello1</p>
    <p :style="{color:activeColor,fontSize: fontsize,'background-color':bgcolor}">hello2</p>

    <p :style="styleObj">hello3</p>

    <div :style="[styleObj,{border:'5px solid blue'}]">hello3</div>
    <div :style="styleOj">hello4</div>
  </div>
</template>

<style>
div {
  padding: 10px;
}
p {
  padding: 10px;
}
</style>

条件渲染

v-if

v-if指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回truthy值的时候渲染

<h1 v-if="awesome">Vue is awesome!</h1>

也可以用v-else添加一个“else块”:

<p v-if="age>=18">我已经是个成年人了</p>
<p v-else>我还是个小朋友</p>

在<template>元素上使用v-if条件渲染分组

因为v-if是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个<template>元素当做不可见的包裹元素,并在上面使用v-if。最终的渲染结果不包含<template>元素。

v-else

你可以使用v-else指令来表示v-if的“else块”

v-else-if

v-else-if,顾名思义,充当v-if的“else-if”块,并且可以连续使用

v-show

另一个用于条件性展示元素的选项是v-show指令。用法大致一样:

<h1 v-show="ok">hello!</h1>

不同的是带有v-show的元素始终会被渲染并保留在DOM中。v-show只是简单地切换元素的display CSS property.
注意,v-show不支持<template>元素,也不支持v-else

v-if VS v-show

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

v-show只是简单地切换元素的display CSS property,带有v-show的元素始终会被渲染并保留在DOM中,频繁切换状态。
v-if:只有后面为false,对应的元素以及子元素都不会被渲染,控制dom元素的创建和销毁,运行时条件很少改变,一次性的。

<script>
   export default{
     data(){
      return{
        age:18,
        sex:'man',
        isShow:true,
      };
     },
   }
</script>

<template>
  <div>
   <!-- <p v-if="age>=18">我已经是个成年人了</p>
   <p v-if="age<18">我还是个小朋友</p> -->

   <p v-if="age>=18">我已经是个成年人了</p>
   <p v-else>我还是个小朋友</p>

   <p v-if="age>18">我已经是个成年人了</p>
   <p v-else-if="age == 18">我今天正好18岁</p>
   <p v-else>我还是个小朋友</p>

   <template v-if="age>=18">
    <p>你好</p>
    <p>你好</p>
    <p>你好</p>
    <p>你好</p>
    <p>你好</p>
   </template>

   <p v-show="sex=='man'">男生</p>
   <p v-show="sex=='woman'">女生</p>
   <button @click="sex = 'woman'">改变</button>

   <h2 v-show ="isShow" >标签1-v-if</h2>
   <h2 v-show ="!isShow" >标签2-v-show</h2>
   <button @click="isShow=!isShow">改变标签</button>


  </div>
</template>

<style>
body {
  display: block;
}
#app {
  text-align: left;
}
</style>

列表渲染

用v-for把一个数组映射为一组元素

我们可以用v-for指令基于一个数组来渲染一个列表。v-for指令需要使用item in items 形式的特殊语法,其他items是源数据数组,而item则是被迭代的数组元素的别名.
在v-for块中,我们可以访问所有父作用域的property。v-for还支持一个可选的第二个参数,即当前项的索引。
你也可以用of替代in作为分隔符,因为它更接近JavaScript迭代器的语法

在v-for里使用对象

你也可以用v-for来遍历一个对象的property
你也可以提供第二个的参数为property名称(也就是键名key)
还可以用第三个参数作为索引

<script>
   export default{
     data(){
      return{
        person:[{name:'张三'},{name:'李四'},{name:'王五'}],
        persons:['张三','李四','王五'],
        personObj:{
          name:'张三',
          age:18,
          sex:'男'
        }
      };
     },
   }
</script>

<template>
  <div>
   <ul>
    <!--<li v-for="item in person">{{ item }} </li>  -->
    <li v-for="item in person" :key="item">{{ item.name}}</li>
   </ul>

   <ul>
    <li v-for="(item,index) in person" :key="item">{{ item.name}}-->{{ index }}</li>
   </ul>

   <!-- v-for使用数组,item代表数组中每一个元素,index表示数组元素的下标 -->
   <ul>
    <li v-for="(item,index) of persons" :key="item">{{ item}}-->{{ index }}</li>
   </ul>

   <!-- v-for使用对象,item表示键值,key表示键名 -->

   <ul>
    <li v-for="(item,key) of personObj" :key="key">{{ item}}-->{{ key }}</li>
   </ul>

   <ul>
    <li v-for="(item,key,index) of personObj" :key="index">{{ item}}-->{{ index }}-->{{ key }}</li>
   </ul>

  </div>
</template>

<style>
body {
  display: block;
}
#app {
  text-align: left;
}
</style>

维护状态

当Vue正在更新使用v-for渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而且就地更新每个元素,并且确保它们在每个索引位置正确渲染。
这个默认的模式是高校的,但是只适用于不依赖子组件状态或临时DOM状态(例如:表单输入值)的列表渲染输出
为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一的key attribute:

<div v-for="item is items" :key="item.id">
  <!-- 内容 -->
</div>

建议尽可能在使用v-for时提供key attribute,除非遍历输出的DOM内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
因为它是Vue识别节点的一个通用机制,key并不仅与v-for特别关联。

为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,key:唯一标识。
快速找到节点,减少渲染次数,提升渲染性能。

<script>
   export default{
     data(){
      return{
        person:[{name:'张三'},{name:'李四'},{name:'王五'}],
        persons:['张三','李四','王五'],
        personObj:{
          name:'张三',
          age:18,
          sex:'男'
        }
      };
     },
     methods:{
      clickHandle() {
        this.persons.unshift('赵六');
      },
     }
   }
</script>

<template>
  <div>
   <ul>
    <!--<li v-for="item in person">{{ item }} </li>  -->
    <li v-for="item in person" :key="item">{{ item.name}}</li>
   </ul>

   <ul>
    <li v-for="(item,index) in person" :key="item">{{ item.name}}-->{{ index }}</li>
   </ul>

   <!-- v-for使用数组,item代表数组中每一个元素,index表示数组元素的下标 -->
   <ul>
    <li v-for="(item,index) of persons" :key="item">{{ item}}-->{{ index }}</li>
   </ul>

   <!-- v-for使用对象,item表示键值,key表示键名 -->

   <ul>
    <li v-for="(item,key) of personObj" :key="key">{{ item}}-->{{ key }}</li>
   </ul>

   <ul>
    <li v-for="(item,key,index) of personObj" :key="index">{{ item}}-->{{ index }}-->{{ key }}</li>
   </ul>

   <ul>
    <li v-for="item of persons" ><input type="checkbox" name="" id=""/>{{ item}}</li>
   </ul>

   <button @click="clickHandle">添加</button>
  </div>
</template>

<style>
body {
  display: block;
}
#app {
  text-align: left;
}
</style>

数组更新检测

变更方法

Vue将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

替换数组

变更方法,顾名思义,会变更调用了这些方法的原始数组。相比之下,也有飞变更方法,例如:filter()、concat()、和slice()。它们不会变更原始数组,而总是返回一个新数组。当使用非变更方法时,可以使用新数组替换旧数组。

example1.items = example1.items.filter(item => item.message.match(/Foo/))
<script>
   export default{
     data(){
      return{
        list:[1,3,5,2,4]
      };
     },
     methods:{
      changelist :function(){
        // vue3可以通过索引值去修改数组
        // this.list[5] = 7;
        // push();给数组末尾添加元素
        // this.list.push(7);
        // pop();删除数组最末尾的元素
        // this.list.pop();
        // shift();给数组的第一位进行删除
        // this.list.shift();
        // unshift();给数组首位开始添加元素
        // this.list.unshift(6,7,8);
        // splict();删除元素、插入元素、替换元素
        // 第一个参数:表示开始插入或开始删除的元素的位置下标
        // 删除元素:第二个参数:表示传入要删除几个元素,如果没有传就删除后面所有的元素
        // this.list.splice(1,2);
        // 插入元素:第二个参数:传入0,并且后面街上要插入的元素
        // this.list.splice(1,0,6,7,8);
        // 替换元素:第二个参数:表示我们替换几个元素,后面的参数表示用于替换前面的圆度
        // this.list.splice(1,3,7,8,9)
        // sort(); 排序
        this.list.sort();
        // reverse(); 翻转
        this.list.reverse();
      }
     }
   }
</script>

<template>
  <div>
   <ul>
    <li v-for="item in list" :key="item">{{ item }}</li>
   </ul>
   <button @click="changelist">改变数组</button>
  </div>
</template>

<style>
body {
  display: block;
}
#app {
  text-align: left;
}
</style>

事件处理

监听事件

我们可以使用v-on指令(通常缩写为@符号)来监听DOM事件,并在触发事件时执行一些JavaScript。用法为:v-on:click="methodName"或使用快捷方式:@click=“methodName”

事件处理方法

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

内联处理器中的方法

除了直接绑定到一个方法,也可以在内联JavaScript语句中调用方法

有时也需要在内联语句处理器中访问原始的DOM事件。可以用特殊变量$event把它传入方法

多事件处理器

事件处理程序中可以有多个方法,这些方法由逗号运算符分隔。

<script>
   export default{
     data(){
      return{
        counter:0,
        age:18,
      };
     },
     methods:{
      // addcounter:function(number1){
        // this.counter+=number1;
      // }

      addcounter:function(number1,e){
        this.counter+=number1;
        console.log(e);
      },

      addage(){
        this.age ++;
      }
     }
   }
</script>

<template>
  <div>
    <!-- 绑定事件 直接通过js处理 -->
   <h2 @click="counter++">{{ counter }}</h2>

   <!-- 绑定事件 -->
   <!-- <h2 @click="addcounter">{{ counter }}</h2> -->

   <h2 @click="addcounter(5)">{{ counter }}</h2>

   <h2 @click="addcounter(5,$event)">{{ counter }}</h2>

   <h2 @click="addcounter(5),addage()">{{ counter }} -- > {{ age }}</h2>
  </div>
</template>

<style>
body {
  display: block;
}
#app {
  text-align: left;
}
</style>

事件修饰符

在事件处理程序中调用event.preventDefault() 或event.stopPropagation()是非常常见的需求。尽管我们可以在方法中轻松实现这点,但是更好的方式是:方法只有纯粹的数据逻辑,而不是处理DOM事件细节。

为了解决这个问题,Vue.js为v-on提供了事件修饰符。修饰符是由点开头的指令后缀来表示的。

.stop   阻止单击事件继续冒泡
.prevent  提交事件不再重载页面
.capture  添加事件监听时使用事件捕获模式,即内部元素触发的事件先在此处理,然后才交由内部元素进行处理
.self  只当在event.target是当前元素自身时触发处理函数,即事件不是从内部元素触发的
.once   只触发一次回调,不像其他只能对原生的DOM事件起作用的修饰符,.once修饰符还能被用到自定义的组件事件上。
.passive  Vue对应addEventListener中的passive选项提供了.passive修饰符。.passive修饰符尤其能够提升移动端的性能
<script>
   export default{
     data(){
      return{
        counter:0,
        age:18,
      };
     },
     methods:{
      divclick(){
        console.log('父元素');
      },

      btnclick(){
        console.log('子元素');
      },

      inputclick(){
        console.log("提交数据成功");
      },

      onceclick(){
        console.log("只触发一次once");
      },
      keyup(){
        console.log("键盘按下,数据提交成功");
      }
     }
   }
</script>

<template>
  <div>
   <!-- 事件修饰符 -->
   <!-- .stop  阻止事件冒泡 -->
   <div @click="divclick">
    <button @click.stop="btnclick">按钮</button>
   </div>

   <!-- .prevent  阻止默认行为 -->
   <form action="">
     <input type="submit" value="提交" @click.prevent="inputclick">  
  </form>

  <!-- .once 只触发一次回调 -->
  <button @click.once="onceclick">只触发一次once</button>

  <!-- .{keyCoude(键盘编码)  |   keyAlias(键盘的简写或别名) } 监听键盘的某个键帽 -->
  <input type="text" @keyup.enter="keyup" >
  </div>
</template>

<style>
body {
  display: block;
}
#app {
  text-align: left;
}
</style>

TIP

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用@click.prevent.self会阻止元素本身及其子元素的点击的默认行为,而@click.self.prevent智慧阻止对元素自身的点击的默认行为。

不要把.passive 和 .prevent一起使用,因为.prevent将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive会告诉浏览器你不想阻止事件的默认行为。

按键修饰符

在监听键盘事件时,我们经常需要检查特定的按键。Vue允许为v-on或@在监听键盘事件时添加按键修饰符

你可以直接将keyboardEvent.key 暴露的任意有效按键名转换为kebab-case来作为修饰符

按键别名

Vue 为最常用的键提供了别名:

  • .enter
  • .tab
  • .delete
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

表单输入绑定

基础用法

你可以用v-model指令在表单<input>、<textarea>及<select>元素上创建双向数据绑定。它会根据空间类型自动选取正确的方法来更新元素。尽管有些神奇,但v-model本质上不过是语法糖。它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理。
v-model的原理:本质上就是两个操作

  1. v-bind绑定一个value属性
  2. v-on给当前元素添加一个input事件

TIP

v-model会忽略所有表单元素的value、checked、selected attribute的初始值。它将始终将当前活动实例的数据作为数据来源。你应该通过JavaScript在组件的data选项中声明初始值。

v-model在内部为不同的输入元素使用不同的property并抛出不同的事件:

  • text和textarea元素使用value property和input事件;
  • checkbox和radio使用checked property和change事件;
  • select字段将value作为prop 并将change作为事件。

提示

对于需要使用输入法(如中文、日文、韩文等)的语言,你会发现v-model不会在输入法组织文字过程中得到更新。如果你也想相应这些更新,请使用input事件监听器和value绑定来替代v-model。

对于单选框,复选框以及选择框的选项,v-model绑定的值通常是静态字符串(对于复选框也可以是布尔值)

<script>
   export default{
     data(){
      return{
       msg:'HelloWorld',
       checked:"",
       fruits:[],
       fruitdate:[{label:"苹果",value: "apple"},{label: "橘子",value:"orange"}],
       sex:"",
       city:"",
       cityed:[{citys:'重庆'},{citys:'江苏'},{citys:'四川'}],
       citydate:[],
      };
     },
     methods:{
      changvalue(e){
        console.log(e.target.value);
        this.msg = e.target.value;
      }
     }
   }
</script>

<template>
  <div>
    <!-- 文本 -->

    <input type="text" v-model = "msg">
    <h2>{{  msg }}</h2>

    <input type="text" :value="msg" @input="changvalue">

    <br/>

    <!-- 复选框 -->
    <!-- 单个勾选框,v-model为布尔值 -->
    <input type="checkbox" v-model = "checked">
    <h2>{{ checked }}</h2>

    <!-- 多个勾选框 -->
    <span v-for="item in fruitdate">
      <input type="checkbox" v-model="fruits" :value="item.value">{{ item.label }}
    </span>
    <h2>我最喜欢的水果是{{ fruits }}</h2>

    <!-- 单选框 -->
    <input type="radio" v-model="sex" value=""><input type="radio" v-model="sex" value=""><h2>{{ sex }}</h2>

    <!-- 选项框 -->
    <!-- 单选 -->
    <select v-model="city">
      <option v-for="item in cityed" :value="item.citys">{{ item.citys }}</option>
    </select>
    <h2>{{ city }}</h2>


    <select v-model="citydate" multiple>
      <option v-for="item in cityed" :value="item.citys">{{ item.citys }}</option>
    </select>
    <h2>{{ citydate }}</h2>
  </div>
</template>

<style>
body {
  display: block;
}
#app {
  text-align: left;
}
</style>

修饰符

.lazy

在默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步(除了上述输入法组织文字时)。你可以添加lazy修饰符,从而转为change事件之后进行同步。

.lazy 当输入框失去焦点,再去同步输入框中的数据
<input v-model.lazy = "msg" />

number

如果想自动将用户的输入值转换为数值类型,可以给v-model 添加number修饰符

.number将输入框的内容自动转换为数字类型
<input v-model.number = "age" type="text"/>

当输入类型为text时这通常很有用。如果输入类型是number,Vue能够自动将原始字符串转换为数字,无需为v-model添加.number修饰符。如果这个值无法被parseFloat()解析,则返回原始的值。

.trim

如果要自动过滤用户输入的首尾空白字符,可以给v-model添加trim修饰符

.trim:自动过滤用户输入的首尾空白字符
<input v-model.trim="msg" />

组件

组件的组织

通常一个应用会以一棵嵌套的组件树的形式来组织:
在这里插入图片描述

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其他的像导航链接、博文之类的组件。

为了能在模板中使用这些组件必须先注册以便Vue能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只通过component方法全局注册的。全局注册的组件可以在应用中的任何组件的模板使用。

组件是带有名称的可复用的实例,单独功能模板的封装。

通过Prop向子组件传递数据

Prop是你可以在组件上注册的一些自定义attribute。我们可以用props选项将其包含在该组件可接收的prop里表中列表中。
当一个值被传递给一个prop attribute时,它就成为该组织实例中的一个property。该property的值可以在模板中访问,就像任何其他组件property一样。
一个组件可以拥有任意数量的prop,并且在默认情况下,无论任何值都可以传递给prop。

Props

prop类型

目前为止,我们只看到了以字符串数组形式列出的prop

props:['title','likes','isPublished','commentIds','author']

但是,通常你希望每个prop都有指定的值类型。这时,你可以以对象形式列出prop,这些property的名称和值分别是prop各自的名称和类型。

这不仅为你的组件提供了文档,还会在它们遇到错误的类型时从浏览器的JavaScript控制台提示用户。

Prop验证

我们可以为组件的prop指定验证的要求,例如你知道的这些类型。如果有一个要求没有被满足,则Vue会在浏览器控制台中警告你。
为了定制prop的验证方法,你可以为props中的值提供一个带有验证要求的对象,而不是一个字符串数组。

export default {
  props: {
    // 基础类型检查
    //(给出 `null` 和 `undefined` 值则会跳过任何类型检查)
    propA: Number,
    // 多种可能的类型
    propB: [String, Number],
    // 必传,且为 String 类型
    propC: {
      type: String,
      required: true
    },
    // Number 类型的默认值
    propD: {
      type: Number,
      default: 100
    },
    // 对象类型的默认值
    propE: {
      type: Object,
      // 对象或者数组应当用工厂函数返回。
      // 工厂函数会收到组件所接收的原始 props
      // 作为参数
      default(rawProps) {
        return { message: 'hello' }
      }
    },
    // 自定义类型校验函数
    propF: {
      validator(value) {
        // The value must match one of these strings
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // 函数类型的默认值
    propG: {
      type: Function,
      // 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
      default() {
        return 'Default function'
      }
    }
  }
}

单向数据流

所有的prop都使得其父子prop之间形成了一个单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
另外,每次父级组件发生变更时,子组件中所有的prop都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变prop。如果你这样做了,Vue就会在浏览器的控制台中发出警告。
这里有两种常见的试图变更一个prop的情形:

  1. 这个prop用来传递一个初始值;这个子组件接下来希望将其作为一个本地的prop数据来使用。在这种情况下,最好定义一个本地的 data property并将这个prop作为其初始值。
  2. 需要对传入的prop值做进一步的转换。在这种情况下,最好是基于该prop值定义一个计算属性

监听子组件

我们在开发中<blog-post>组件时,它的一些功能可能需要与父级组件进行沟通。

1、在子组件中可以通过$emit来触发事件

this.$emit('自定义事件的名称','发送事件参数')
this.$emit('injectMsg',this.msg)

2、在父组件中,通过v-on监听子组件中的自定义事件

父子组件的访问方式:
父组件访问子组件:$refs:开发中常用
                 ref:用来给元素或者子组件注册引用信息
子组件访问父组件:$parent,在开发中尽量少用,组件的复用性很高
子组件访问根组件:$root

通过插槽分发内容

和HTML元素一样,我们经常需要向一个组件传递内容,这可以通过使用Vue的自定义<slot>元素来实现。

插槽

插槽内容

Vue实现了一套内容分发的API,这套API的设计灵感源自Web Components规范草案。将<slot>元素作为承载分发内容的出口。

渲染作用域

当你想在一个插槽中使用数据时,该插槽可以访问与模板其余部分相同的实例property(即相同的“作用域”)
插槽不能访问<todo-button>的作用域
在这里插入图片描述

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

具名插槽

有时我们需要多个插槽,对于这样的情况,<slot>元素有一个特殊的attribute:name。通过它可以为不同的插槽分配独立的ID。

一个不带name的<slot>出口会带有隐含的名字“default”。
在向具名插槽提供内容的时候,我们可以在一个<template>元素上使用v-slot指令,并以v-slot的参数形式提供其名称。

注意:v-slot 只能添加在<template>上

备用内容

有时为一个插槽指定备用(也就是默认的)内容是很有用的,它只会在没有提供内容的时候被渲染。

作用域插槽

有时让插槽内容能够访问子组件中才有的数据是很有用的。当一个组件被用来渲染一个项目数组时,这是一个常见的情况,我们希望能够自定义每个项目的渲染方式。

绑定在<slot>元素上的attribute被称为插槽prop。现在,在父级作用域中,我们可以使用带值的v-slot来定义我们提供的插槽prop的名字。

作用域插槽:父组件替换插槽的标签,但是数据由子组件来提供。

APP.vue

<script>
import Content from './components/Content.vue';
export default{
  data(){
    return{

    }
  },
  components:{
    Content
  }
}
</script>

<template>
  <div>
    <Content><button>按钮</button></Content>
    <Content><input type="text"/></Content>
    <!-- 如果有多个值,同时放入组件进行替换时,一起作为替换元素 -->
    <Content><button>按钮</button><input type="text"/><h2>插槽</h2></Content>

    <Content>
      <template v-slot:button></template>
      <!-- <template v-slot:button><button>按钮</button></template> -->
      <template v-slot:input><input type="text"/></template>
      <template v-slot:h2><h2>插槽</h2></template>
    </Content>

    <Content>
      <template v-slot:default="SlotProps">
        <ul>
          <li v-for="item in SlotProps.list"  >{{ item }}</li>
        </ul>
      </template>
    </Content>

    <Content>
      <template v-slot:default="SlotProps">
        <ol>
          <li v-for="item in SlotProps.list"  >{{ item }}</li>
        </ol>
      </template>
    </Content>
  </div>
</template>

<style>
</style>

Content.vue

<template>
    <h2>我是一个Content组件</h2>
    <div>  
        <!-- <slot name="button"><button>默认按钮</button></slot>
        <slot name="input"></slot>
        <slot name="h2"></slot> -->
        <slot :list="list"></slot>
    </div>
</template>
<script>
export default{
    data(){
        return{
            list:[1,2,3,4,5]
        }
    }
}
</script>

Provide / Inject

通常,当我们需要从父组件向子组件传递数据时,我们使用props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将prop沿着组件链逐级传递下去,可能会很麻烦。
对于这种情况,我们可以使用一对 provide 和 inject 。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。

实际上,你可以将依赖注入看作是“长距离的prop”,除了:

  • 父组件不需要知道哪些子组件使用了它provide的property
  • 子组件不需要知道inject的property来自哪里
provide(){
   return {
        message:this.message
   }
},

处理响应性

在默认情况下,provide / inject 绑定并不是响应式。我们可以通过传递一个ref property 或 reactive 对象给 provide 来改变这种行为。

homeview.vue

<script>
import Content from './Content.vue';
export default{
    data(){
        return{
            message:"HelloWorld",
            obj:{
                message:"HelloWorld"
            },
        }
    },

    components:{
        Content
    },

    provide(){
        return{
            // message: this.obj,
            message:()=>this.message,
        }
        
    }
}
</script>

<template>
    <div>
        <content/>
        <!-- <button @click="obj.message='你好'">改变helloworld</button>
        <h2>HomeView---  {{ obj.message }}</h2> -->

        <button @click="message='你好'">改变helloworld</button>
        <h2>HomeView--->  {{ message }}</h2>


    </div>
</template>

hello.vue

<template>
    <div>
        <h2>我是Hello组件</h2>
        <!-- <h2>hello---  {{ message.message }}</h2> -->

        <h2>hello -- >{{ newMsg }}</h2>
    </div>
</template>

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


    computed:{
        newMsg(){
            return this.message();
        }
    },

    inject:["message"]
}
</script>

content.vue

<template>
    <div>
        <Hello></Hello>
    </div>
</template>

<script>
import Hello from './Hello.vue';
export default{
    data(){
        return{

        }
    },

    components:{
        Hello
    }
}
</script>

App.vue

<script>
import HomeView from './components/HomeView.vue';
export default{
  data(){
    return{

    }
  },

  components:{
    HomeView
  }
}
</script>

<template>
  <div>
    <HomeView></HomeView>
  </div>
</template>

<style>
</style>

生命周期钩子

每个组件在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到DOM并在数据变化时更新DOM等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己代码的机会。

Hello.vue

<script>
export default{
    data(){
        return {
            counter:0
        }
    },

    beforeCreate(){
        console.log('beforeCreate');
    },

    created(){
        console.log('created');
    },

    beforeMount(){
        console.log('beforeMount');
    },

    mounted(){
        console.log('mounted');
    },

    beforeUpdate() {
        console.log('beforeUpdate');
    },

    updated(){
        console.log('updated');
    },

    beforeUnmount(){
        console.log('boforeUnmount');
    },

    unmounted(){
        console.log('unmounted');
    }
    
}
</script>

<template>
<div>
    <div>helloWorld</div>
    <button @click="counter++">改变counter</button>
    <h2>{{ counter }}</h2>
        
</div>
</template>

App.vue

<script>
import hello from './components/Hello.vue'
export default{
  data(){
    return{
      message:"helloWorld",
      isShow:true
    }
  },
  components:{
    hello
  }
}
</script>

<template>
  <div>
    <hello v-if="isShow"/>
    <button @click="isShow=!isShow">控制</button>


  </div>
</template>

什么是组合式API

通过创建vue组件,我们可以将界面中重复的部分连同其功能一起提取为可重用的代码段。仅此一项就可以使我们的应用在可维护性和灵活性方面走得相当远。处理大型应用时,共享和重用代码变得尤为重要。

该组件有以下几个职责:

  1. 从假定的外部API获取该用户的仓库,并在用户有任何更改时进行刷新
  2. 使用searchQuery字符串搜索仓库
  3. 使用filters对象筛选仓库

使用(data、computed、methods、watch)组件选项来组织逻辑通常都很有效。然而,当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件 难以阅读和理解。

组合式API基础

既然我们知道了为什么,我们就可以知道怎么做。为了开始使用组合API,我们首先需要一个实际使用它的地方。在Vue组件中,我们将此位置称为setup

setup组件选项

新的setup选项在组件被创建之前执行,一旦props被解析完成,它就将被作为组合式API的入口。

WARNING

在setup中你应该避免使用this,因为它不会找到组件实例。setup的调用发生在data、property、computed property 或 methods被解析之前,所以它们无法在setup中被获取。

setup选项是一个接收props 和 context 的函数。我们将setup返回的所有内容都暴露给组件的其余部分(计算属性、方法、生命周期钩子等等)以及组件的模板。

TIP

因为setup是围绕beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在setup函数中编写。

带 ref 的响应式变量

在Vue 3.0中,我们可以通过一个新的ref函数使任何响应式变量在任何地方起作用

import {ref} fromm 'vue'

const counter = ref(0)

ref接收参数并将其包裹在一个带有value property 的对象中返回,然后可以使用该property访问或更改响应式变量的值。

将值封装在一个对象中,看似没有必要,但为了保持JavaScript中不同数据类型的行为统一,这是必须的。这是因为在JavaScript中,Number 或 String 等基本类型是通过值而非引用传递的。

提示

换句话说,ref为我们的值创建了一个响应式引用。在整个组合式API中会经常使用引用的概念。

watch响应式更改

就像我们在组件中使用watch选项并在user property上设置侦听器一样,我们也可以使用从Vue导入的watch函数执行相同的操作。它接受3个参数:

  1. 一个想要侦听的响应式引用或getter函数
  2. 一个回调
  3. 可选的配置选项
watch和watchEffect的区别:
1.watchEffect 不需要指定监听的属性,自动收集依赖,只要在回调中引用到了响应式的属性,只要这些属性发生改变,回调就会执行,watch只能侦听指定的属性,做出回调函数的执行,可以侦听多个,Vue3开始后
2.watch可以获取到新值和旧值,watchEffect拿不到
3.watchEffect在组件初始化的时候就会自动执行一次,用来收集依赖,watch不需要,一开始就指定了

独立的 computed 属性

与ref 和watch类似,也可以使用从Vue导入computed函数在Vue组件外部创建计算属性。
computed函数的第一个参数:类似于getter的回调函数,输出的是一个只读的响应式引用 。为了访问新创建的计算变量的value,我们需要像ref一样使用 .value property。

<script>
import {ref,reactive,toRefs,watch,watchEffect,computed} from "vue";
import Content from "./components/Content.vue";
export default {
  data(){
    return{

    };
  },

  setup(){
    console.log('setup');
    let msg = 'Hello';
    console.log(msg);
    function changMsg(){
      msg:"你好";  //数据不是响应式的
    }

    // 通过ref定义响应式变量

    const counter = ref(0);
    function changCounter(){
      counter.value++;
    }

    watch(counter,(newVal,oldVal) =>{
      console.log(newVal);
      console.log(oldVal);
    });

    // 通过reactive定义响应式的引用类型的数据
    const obj=reactive({
      name:"张三" ,
      age : 18 ,
      children:{
        name:"小张"
      }
    });

    function changeObjName(){
      obj.name="李四"
    }

    // toRefs(Object)使解构后的数据重新获得响应式


    let{name,children} = toRefs(obj);


    // watch(侦听的响应式引用,回调函数)


    watch(obj,(newVal,oldValue) =>{
      console.log(newVal);
      console.log(oldValue);
    });

    // watchEffect(回调函数)  注意:不需要指定监听的属性,组件初始化时会执行一次回调函数,自动收集依赖
    watchEffect(()=>{
      console.log(obj.name)
    })

    const msg1 = ref('helloworld');
    const reversemsg1=computed(()=>{
      return msg1.value.split("").reverse().join("");
    });

    console.log(reversemsg1.value);

    // 通过ES6运算符进行解构使得对象中的属性不是响应式的
    return {msg,changMsg,counter,changCounter,obj,changeObjName,...toRefs(obj),name,children,watch};
  },

  beforeCreate() {
    console.log('beforeCreate');
  },

  created() {
    console.log('Created')
  },

  components:{
    Content,
  }

}
</script>

<template>
  <div>
    <h2>{{msg}}</h2>
    <button @click="changMsg">改变msg</button>

    <h2>{{counter}}</h2>
    <button @click="changCounter">改变counter</button>

    <h2>{{name}}</h2>
    <button @click="changeObjName">改变名字</button>

    <h2>{{children.name}}</h2>
    <content/>
  </div>
</template>
<script>
import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated} from 'vue'
export default {
  data(){
    return{

    }
  },

  setup(){

    // 生命周期钩子调用:函数中有一个参数:回调函数
    onBeforeMount(() =>{
      console.log('onBeforeMount')
    });

    onMounted(() =>{
      console.log('onMounted')
    });

    onBeforeMount(() =>{
      console.log('onBeforeMount')
    })
  }
}
</script>

setUp

参数

使用setup函数时,它将接收两个参数:
1.props
2.context

Props

setup函数中的第一个参数是props。正如在一个标准组件中所期望的那样,setup函数中的props是响应式的,当传入新的props时,它将会被更新。

TIP

因为setup是围绕beforeCreate 和created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在setup函数中编写。

WARNING

因为props是响应式的,你不能使用ES6解构,它会消除props的响应性
如果需要解构prop,可以在setup函数中使用toRefs 函数来完成。

Context

传递给setup函数的第二个参数是:context。Context是一个普通JavaScript对象,暴露了其他可能在setup中有用的值

 setup(props,context){
    // Attribute(非响应式对象,等同于$attrs)
    console.log(context.attrs)
    
  //   插槽(非响应式对象,等同于$slots)
    console.log(context.slots)
    
  //   触发事件(方法,等同于$emit)
    console.log(context.emit)
    
  //   暴露公共property(函数)
    console.log(context.expose)
  }

context是一个普通的JavaScript对象,也就是说,它不是响应式的,这意味着你可以安全地对context使用ES6解构

attrs 和 slots 是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以attrs.x 或 slots.x 的方式引用property。请注意,与props不同,attrs 和 slots 的property是非响应式的。如果你打算根据attrs 或 slots 的更改应用副作用,那么应该在onBeforeUpdate 生命周期钩子中执行此操作。

App.vue

<script>
import {ref} from "vue";
import content from "./components/Content.vue";
export default {
  data(){
    return{
      message:"helloworld",
    }
  },

  methods:{
    injectCounter(value){
      console.log(value)
    }
  },
  
  mounted() {
    console.log(this.$refs.content);
    this.$refs.content.sendParent()
  },

  components:{
    content
  }
}
</script>

<template>
  <div>
<!--    <content :message="message" class="box" id="content" @injectCounter="injectCounter"/>-->
    <content :message="message" class="box" id="content" />
    <content :message="message" class="box" id="content" ref="content" @injectCounter="injectCounter"/>
    <button @click="message='你好'">改变message</button>
  </div>
</template>

Content.vue

<script>
import {onUpdated,toRefs,ref,h} from 'vue'
export default {
  props:{
    message:{
      type:String,
      default:"你好",
    }
  },

  setup(props,context){
    // console.log(props);
    // const {message}=toRefs(props);
    // console.log(message.value)
    // onUpdated(()=>{
    //   console.log(message.value)
  // })
    const counter = ref(20)

    function sendParent(){
      context.emit('injectCounter',counter.value)
    };

    context.expose({
      sendParent,counter
    })

    console.log(context.attrs);
    return ()=>h('div',counter.value)

  },

  data(){
    return{

    }
  }
}
</script>

<template>
  <div>
    <h2>我是content组件内容</h2>
    <h2>{{message}}</h2>
    <button @click="sendParent">提交数据</button>
  </div>
</template>

<style></style>

访问组件的property

执行setup时,你只能访问一下property:

  • props
  • attrs
  • slots
  • emit
    换句话说,你将无法访问以下组件选项
  • data
  • computed
  • methods
  • refs(模板ref)

使用渲染函数

setup可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态.

返回一个渲染函数将阻止我们返回任何其他东西。从内部来说,这不应该成为一个问题,但当我们想要将这个组件的方法通过模板ref暴露给父组件时就不一样了。
我们可以通过调用expose来解决这个问题,给它传递一个对象,其中定义的property将可以外部组件实例的访问。

结合模板使用

如果setup返回一个对象,那么该对象的property以及传递给setup的props参数中的property就都可以在模板中访问到。
注意,从setup返回的refs在模板中访问时是自动浅解包的,因此不应在模板中使用.vue

使用this

在setup()内部,this不是该活跃实例的引用,因为setup()是在解析其他组件选项之前被调用的,所以setup()内部的this的行为与其他选项中的this完全不同。这使得setup()在和其他选项式API一起使用时可能会导致混淆

Provide / Inject

我们也可以在组合式API中使用provide/inject。两者都只能在当前活动实例的setup()期间调用

使用Provide

在setup()中使用provide时,我们首先从vue显式导入provide方法。这使我们能够调用provide来定义每个property

provide函数允许你通过两个参数定义property:

  1. name(<string>类型)
  2. value

使用Inject

在setup()中使用inject时,也需要从vue显式导入。导入以后,我们就可以调用它来定义暴露给我们的组件方式。

inject函数有两个参数:

  1. 要inject的property的name
  2. 默认值(可选)

响应性

添加响应性

为了增加provide 值 和inject值之间的响应性,我们可以在provide值时使用ref 或 reactive

单文件组件

介绍

Vue单文件组件(又名*.vue文件,缩写为SFC)是一种特殊的文件格式,它允许将Vue组件的模板、逻辑与样式封装在单个文件中。

Vue SFC是经典的HTML、CSS与JavaScript三个经典组合的自然延伸。每个*.vue文件由三种类型的顶层代码块组成:<template>、<script> 与 <style>

  • <script>部分是一个标准的JavaScript模块。它应该导出一个Vue组件定义作为其默认导出
  • <template>部分定义了组件的模板
  • <style>部分定义了与此组件关联的CSS

工作原理

Vue SFC 是框架指定的文件格式,必须由@vue/compiler-sfc 预编译标准的JavaScript与CSS编译后的SFC是一个标准的JavaScript(ES)模块——这意味着通过正确的构建配置,可以像模块一样导入SFC

SFC语法规范

介绍

*.vue文件是使用类HTML语法来描述Vue组件的一种自定义文件格式。每一个*.vue文件都由三中类型的顶层语法块所组成:<template> 、<script>、<style>以及可选的附加自定义块

单文件组件<script setup>

<script setup>是在单文件组件(SFC)中使用组合式API的编译时语法糖。相比于普通的<script>语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码
  • 能够使用纯Typescript声明props和抛出事件
  • 更好的运行时性能(其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)
  • 更好的IDE类型推断性能(减少语言服务器从代码中抽离类型的工作)

基本语法

要使用这个语法,需要将setup attribute 添加到 <script>代码块上:

<script setup>
console.log("hello script setup")
</script>

里面的代码会被编译成组件setup()函数的内容。这意味着与普通的<script>只在组件被首次引入的时候执行过一次不同,<script setup>中的代码会在每次组件实例被创建的时候执行

顶层的绑定会被暴露给模板

当使用<script setup>的时候,任何在<script setup>声明的顶层的绑定(包括变量,函数声明,以及import引入的内容)都能在模板中直接使用。

顶层的绑定会被暴露给模板
定义响应式的变量,还是需要从Vue中引入
引入组件,不需要注册
定义变量,在模板使用不需要暴露了出去,模板直接使用

import 导入的内容也会以同样的方式暴露。意味着可以在模板表达式中直接使用导入的helper函数,并不需要通过methods选项来暴露它

响应式

响应式状态需要明确使用响应式APIs 来创建。和从setup()函数中返回值一样,ref值在模板中使用的时候会自动解包

使用组件

<script setup>范围里的值也能被直接作为自定义组件的标签名使用
如果你使用过JSX,在这里的使用它的心智模型是一样的。其kebab-case格式的<my-component>同样能在模板中使用。不过,我们强烈建议使用PascalCase格式以保持一致性。同时也有助于区分原生的自定义元素。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值