vue
1、通过 CDN 来使用 Vue
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<div id="app">{{ message }}</div>
<script>
const { createApp } = Vue //不同之处
createApp({data() {return { message: 'Hello Vue!'}}}).mount('#app')
</script>
2、使用 ES 模块构建版本(常用)
<div id="app">{{ message }}</div>
<script type="module"> //不同之处
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js' //不同之处
createApp({data() {return {message: 'Hello Vue!'}}}).mount('#app')
</script>
- 服务端语法规范:commonjs规范:module.exports 导出 + require(path)导入
- vue和recat都用的ESM语法规范:export xxx 导出 + import xxx from path导入
3、使用导入映射表导入Import Maps 仅谷歌浏览器支持
<script type="importmap"> //不同之处
{
"imports": {
//起了个名字快速找到 "vue"。别名导入目标模块
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
<div id="app">{{ message }}</div>
<script type="module"> //不同之处
import { createApp } from 'vue' //不同之处
createApp({data() {return {message: 'Hello Vue!'}}}).mount('#app')
</script>
4、拆分模块
<!-- index.html -->
<script type="module">
import { createApp } from 'vue'
import MyComponent from './my-component.js'
createApp(MyComponent).mount('#app')
</script>
<!-- my-component.js -->
export default{ data() {return { count: 0 }},template: `<div>count is {{ count }}</div>`}
5、创建vue应用:
- npm init vue@latest
- 安装项目脚手架工具 起名todolist(待办事项列表)
- cd todolist
- 打开脚手架文件夹
- npm i (全写npm install)
- 下载依赖
- npm run dev
- 快速启动 打包构建
- npm i -D sass (全写:npm i --save-dev)
- 把sass装到开发环境依赖(devDependencies)中
- npm i sass
- 把sass装到生产环境依赖(dependencies)中(sass是css一个预编译语言,不能被浏览器识别,不需用户下载)
- npm uninstall sass
- 卸载sass依赖
拿不准依赖放哪里就放生产环境依赖(dependencies)中,这个里边是用户和程序员都能用得依赖。但是"@vitejs/plugin-vue"和 "vite"要注意安装到开发环境依赖(devDependencies)中。否则用户使用需要下载,代码量太大了
-
package.json 中:(管理包要用的配置文件)
- dev :快速启动脚本
- bulid :构建,但是它只是把app.vue打造成纯html,css,js得纯二阶段工程。然后部署到nginx中。
- preview: 是预览
- dependencies :生产环境依赖
- devDependencies:开发环境依赖
-
vue和react是SPA框架(single page app单页面应用程序)
- 我们要从根目录中写东西.
- index是入口页面,
- src是源代码目录,
- components是组件,
- public用户静态资源,
- assets程序要用静态资源,
- README.md笔记(安装什么依赖,有什么坑等),
- vite.config.js打包配置
-
一个页面做出几十个页面就整个网页得效果就叫单页面应用程序。
-
声明全局组件需要在挂在app身上,在main.js中:
const app = createApp(root); app.mount("#app"); -
否则。根组件的一个子组件需要注册声明后才能部署:在app.vue中:
components:{} -
<style scoped lang="scss"></style>scoped规定样式不能超过作用域。scss是sass引用的语言。 -
可以把每个有复用价值的都拆分成组件
-----------------------
基础
1、 创建一个应用: createApp()全局 API
- 根组件:
<script type="module">
import { createApp } from 'vue'
import App from './App.vue' // 从一个单文件组件中导入根组件
const app = createApp(App)
</script>
-
挂载应用:.mount
<div id="app"></div>app.mount("#app") 应用实例必须在调用了.mount() 方法后才会渲染出来.返回值是根组件实例而非应用实例 -
template选项 用来创建DOM的,例:
template: `<div>count is {{ count }}</div>`
当根组件没有设置 template 选项时,Vue 将自动使用容器的 innerHTML 作为模板
- 应用配置: 应用实例会暴露一个 .config 对象允许我们配置一些应用级的选项,如
>定义一个应用级的错误处理器:errorHandler
app.config.errorHandler = (err) => { /* 处理错误 */ }
>注册一个组件:component
app.component('TodoDeleteButton', TodoDeleteButton)
- 多个应用实例 createApp API 可以在同一个页面中创建多个共存的 Vue 应用。全局使用(开发不推荐)
const app1 = createApp({/* ... */})
app1.mount('#box-1')
const app2 = createApp({/* ... */})
app2.mount('#box-2')
2、 模板语法:{{msg}}/v-bind/v-html/v-on/v-slot/.prevent
-
文本插值: {{ msg }} 双大括号会将数据解释为纯文本,而不是 HTML
-
原始 HTML: v-html 指令:DOM结构插入
<span v-html="rawHtml"></span>显示出来直接是rawHtml的内容。如直接使用{{rawHtml}},显示的就是带有标签的内容。
在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞.永远不要使用用户提供的 HTML 内容。程序员尽在内容安全可靠的情况下可使用 常识: XSS的防御方式=将用户提交内容中的【左右尖括号<>】都进行转义 < >
-
Attribute 绑定: v-bind 指令: 让元素的id属性与组件的dynamicId属性保持一致。如绑定的值是 null 或undefined.直接移除这个attribute
<div v-bind:id="dynamicId"></div>简写指令<div :id="dynamicId"></div>布尔型 Attribute。xxx为真值或一个空字符串按钮不可用。伪值按钮仍可用:<button :disabled="xxx">Button</button> -
动态绑定多个值:
data(){return {objectOfAttrs: {id: 'container' , class: 'wrapper'}}}<div v-bind="objectOfAttrs"></div>通过不带参数的v-bind,将多个属性全绑定到元素。 单独绑定这样写:<div :id="objectOfAttrs.id"></div> -
使用 JavaScript 表达式:
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }} //不能if()else{},只能三元表达式
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>
表达式以组件为作用域解析执行.仅支持 [单一表达式]
-
调用函数
<span :title="toTitleDate(date)"> {{ formatDate(date) }} </span>绑定在表达式中的方法在组件每次更新时都会被重新调用,因此不应该产生任何副作用(改变数据或触发异步操作) -
指令 Directives
内置指令: v-bind(:) 、v-html 、v-for 、v-on(@) 、v-slot 、v-if
参数: 指令名后通过一个 【:】隔开做标识
<a :href="url"></a>href参数告诉v-bind指令将表达式 url 的值绑定到元素的href属性上
动态参数:
<a :[attributeName]="url"> </a><a @[eventName]="doSomething">中括号里的会作为表达式被动态执行。 表达式的值应当是【字符串【或【null(相当于移除该绑定)】。否则会报错。 表达式不可有空格和引号。 表达式避免使用大写,会被浏览器转小写导致功能失效【单文件组件内的模板不受此限制】 组件名与属性名在未用脚手架创建工程时只支持小写和中划线
修饰符:以【.】开头的特殊后缀 .prevent 会告知v-on 指令对触发的事件调用 event.preventDefault()
<form @submit.prevent="onSubmit">...</form>. 完整指令语法: v-on:submit.prevent="onSubmit" ==> 指令名字:事件类型.阻止默认行为=处理逻辑/值 名字和值必须有。
3、 响应式基础 data()/mounted()/methods:{}/nextTick()全局/unmounted()
-
声明响应式状态:
data(){return{}}, mounted()是生命周期钩子。- 例:
export default {data() {return {count: 1}},mounted() {console.log(this.count);this.count = 2}} - 避免在顶层 data 上使用【
$】和【-】。 - 每个组件里的this都是当前组件的实例。
- this.count++ <===> this.$data.count++
- 例:
-
响应式代理vs原始值 :vue3用的proxy代理,做任何事情都不能跨过代理;
export default {
data() {
return {
someObject: {}
}
},
mounted() {
const newObject = {}
this.someObject = newObject
//此时的someObject已经是原来的 newObject 的一个响应式代理出来的值。
console.log(newObject === this.someObject) // false
}
}
-
声明方法:methods:{} 为组件添加方法,是一个包含所有方法的对象
- methods 中的方法绑定了永远指向组件实例的 this。所以里边的函数应该是普通函数。只能在里边函数的函数里边使用箭头函数
methods: {increment() {this.count++}}<button @click="increment">{{ count }}</button>点击时调用increment方法
-
DOM 更新时机(重)nextTick(()=>{}) 全局 API
import { nextTick } from 'vue'
export default {
methods: {
increment() {
this.count++; //数据在这里发生变化了,但是渲染到页面上需要时间(异步)
// 此时同步的立即查看渲染到页面的数据,还是没有改变前的状态。
nextTick(() => {
// 访问更新后的 DOM.innerText
})
}
}
}
- 深层响应性:即使在更改深层次的对象或数组,你的改动也能被检测到。
响应式的数据即状态,立刻访问是还没有更新过来的状态,用nextTick可以获取到改变后的状态(异步渲染) 数据状态可以有深度,但不宜太深,两三层就可以了,否则不方便维护。lodash如果只用防抖节流,就最好不引包自己写出那个封装(包含cancel)
- 有状态方法:
import { debounce } from 'lodash-es'
export default {
created() {
// 每个实例都有了自己的预置防抖的处理函数
this.debouncedClick = _.debounce(this.click, 500)
},
unmounted() {
// 最好是在组件卸载时, 清除掉防抖计时器
this.debouncedClick.cancel()
},
methods: {
click() {
// ... 对点击的响应 ...
}
}
}
- this注意项: 函数里的this是当前组件的实例,要用普通函数才可以。注意箭头函数,无需this时才可以用,方法里边的add里如果需要在定时器(数组的批处理,或自己定义函数)里操作组建状态,必须要写箭头函数,也就是组件内函数里的函数要用箭头函数
4、 计算属性: computed:{}选项 二次数据可以提升性能
- 基础:computed:{}
computed: {
// 一个计算属性的 getter
publishedBooksMessage() {
// this 指向当前组件实例
return this.author.books.length > 0 ? 'Yes' : 'No'
}
<span>{{ publishedBooksMessage }}</span>
-
计算属性值会基于其响应式依赖被缓存
-
可写计算属性 get() / set(newValue)
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
fullName: {
// getter 不应有副作用。不要在 getter 中做异步请求或者更改 DOM
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[this.firstName, this.lastName] = newValue.split(' ')
}
}
}
}
再次获得fullName时,setter会被调用,而firstName和lastName会随之更新
computed组件,防止组建更新,但方法相同未改变还每次被调用。getter就是基于一手数据要二手数据的,其他的操作不要用,不要有副作用,尤其不能给一手数据修改值。这就是指责单一。
5、 类与样式绑定
- class绑定: 绑定对象:
<div :class="{ active: isActive }"></div>active是否存在取决于数据属性 isActive 的真假值。
:class 指令也可以和 class属性共存:
<div class="static" :class="{active: isActive}"></div>
- 也可以直接绑定一个对象:
data(){ return{ classObject:{active:true,'text-danger':false} } 。 <div :class="classObject"></div>
- 可以绑定一个返回对象的计算属性:
data() {return {isActive: true,error: null } },computed: { classObject() {return {active: this.isActive && !this.error,'text-danger': this.error && this.error.type === 'fatal'} } } 。<div :class="classObject"></div>`
-
绑定数组:
<div :class="[activeClass, errorClass]"></div> -
可以使用三元表达式
<div :class="[isActive ? activeClass : '', errorClass]"></div> -
也可以在数组中嵌套对象:
<div :class="[{ active: isActive }, errorClass]"></div> -
在组件上使用: 对于只有一个根元素的组件,当你使用了 class 属性时,这些 class 会被添加到根元素上,并与该元素上已有的 class 合并。
通过组件的 【
$attrs】 属性来实现指定哪个根元素可以来接收这个 class:<p :class="$attrs.class">Hi!</p>
-
绑定内联style: (样式推荐驼峰式和烤串风格(要加引号)写法,模板里要用驼峰式)
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> -
样式多值:
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>数组仅会渲染浏览器支持的最后一个值,在支持不需要特别前缀的浏览器中都会渲染为 flex -
注册组件同时有两个以上组件在里边,写双标签不会有问题,单标签就只允许一个组件。有根组件,还想用属性要设置禁用继承就可以
6、 条件渲染 v-if/v-else/v-else-if/v-show
- v-if:
<h1 v-if="awesome">指令的表达式返回真值时才被渲染这块内容</h1> - v-else:
<button @click="awesome = !awesome">Toggle</button>
<h1 v-if="awesome">指令的表达式返回真值渲染</h1>
<h1 v-else>否则渲染这个</h1>
- v-else-if:
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else-if="type === 'C'">C</div>
<div v-else>Not A/B/C</div>
-
template上的v-if:
<template v-if="ok"><h1>Title</h1><p>con1</p><p>con2</p></template> -
虚拟dom,幽灵标签:在渲染时
<template>是不存在的。即想让一堆元素是一个整体,又不想多加一个dom元素时在逻辑上使用<template>。因为多加一层div,你的dom深度就会加一。dom复杂,浏览器渲染就会复杂。性能就会不好,能够减少diff算法. -
vue和react都是虚拟dom和diff算法的一个框架。把dom树提炼成一个虚拟对象,某一数据发生变化,他会从数据上计算出一个新的虚拟dom,用diff算法比较两个虚拟dom有那些不同,而不是用递归,可以理解为一个优化过的递归,把不同的节点用差量渲染,其他的地方用缓存。这也就是vue和react提升性能的道理。能少用dom就少用,在做差量渲染时,少写一级就少一次渲染,也就是少一次递归过程
-
v-show:
<h1 v-show="ok">指令的表达式返回真值就display:block,否则就隐藏</h1>不支持在<template>元素上使用,也不能和 v-else 搭配使用。 -
【v-if】 vs 【v-show】:
- v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
- v-if 是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。相当于display:none。
- 因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。
-【v-if】 和 【v-for】: 不能同时使用。要分开使用.因为这样二者的优先级不明显
7、 列表渲染 v-for -key的性能优化
item in items 形式的语法 in 或 of
- 数组的遍历:
data() {return {items: [{ message: 'Foo' }, { message: 'Bar' }]}
<li v-for="(item,index) in items"> {{ item.message }}-{{index}} </li>
item也可以解构
-
对象的遍历:
<li v-for="(value,key,index) of myObject">{{ key }}:{{ value }}</li> -
在 v-for 里使用范围值: n初始值为1
<span v-for="n in 10">{{ n }}</span> 遍历1-10 -
可以在
<template>标签上使用 v-for v-for和v-if想要同时使用可以加一层<template>在其上边使用v-for
<template v-for="todo in todos"><li v-if="!todo.isComplete">{{ todo.name }}</li></template>
-
通过 key 管理状态: 大列表一定要加key(key要唯一),加虚拟dom,计算属性
<template v-for="todo in todos" :key="todo.name"><li>{{ todo.name }}</li></template> -
key的作用:
- 在大列表中,key是item的唯一标识(没有两个item的key是相同的)
- 当列表数据发生变化,需要重新渲染时,diff算法的过程如下:
- 只要新老item的key相同,就不再深入对它的虚拟DOM (JS对象〉进行深入递归比较,直接复用老的真实dom
- 只要新老item的key不同,也不再深入递归比较其虚拟DOM,直接将老的真实DOW删除,并原地绘制新的Dom
通俗解释
(一看key不同,就不在老的dom里深层递归了,直接渲染新的dom。每个key值是一定要唯一的。key不会显示在html里。加key,只要key相同里边的东西就相同,就不看里边的东西,直接用上一次的缓存。只要key不同,也不看里边的东西,直接放弃老的替换新的渲染。)
-
组件上使用 v-for: 需要传递 props 将任何数据传递给组件:
-
父组件:声明 :list = todos 通过指令把todos的数据携带给子组件
-
子组件:配合 props:["list"] 接收到父组件的数据,同时可以直接使用该数据
-
子组件:声明 emits:["add"] 用自定义事件和父组件通信emits
-
父组件:接收 @add = "callback" 接收并用一个函数来监听处理这个自定义事件
-
-
数组变化侦测: push()、pop()、shift()、unshift()、splice()、sort()、reverse()这些方法调用直接原数组上变更。 filter(),concat() 和 slice(),这些都不会更改原数组,而总是返回一个新数组。
- 在计算属性中使用reverse() 和 sort()。请在调用这些方法之前创建一个原数组的副本:
- return numbers.reverse() // 直接更改原始数字
- return [...numbers].reverse() // 用副本反转
8、 事件处理 v-on【 @ 】监听 DOM 事件
-
事件处理器的值可以是:
- 内联事件处理器:事件被触发时执行的内联JavaScript语句 (与onclick类似)。
- 方法事件处理器:一个指向组件上定义的方法的属性名或是路径。
-
foo、foo.bar和foo['bar']会被视为方法事件处理器,而 foo() 和 count++ 会被视为内联事件处理器
-
在内联处理器中调用方法: methods: {say(message) {alert(message)} }
<button @click="say('hello')">Say hello</button> -
在内联事件处理器中访问事件参数:
- 访问 DOM 原生事件:
methods: {warn(message, event) {if(event) {event.preventDefault()}alert(message)} }
- 访问 DOM 原生事件:
参数和事件对象e都接收,用
$event。尽量放在形参的最后一个,可传可不传实参,都不影响。放第一个不传参会undefined。
使用特殊的
$event变量:<button @click="warn('无法提交', $event)">Submit</button>
使用内联箭头函数:
<button @click="(event) => warn('无法提交', event)">Submit</button>
- 事件修饰符: .stop/.prevent/.self/.capture/.once/.passive
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当末梢元素是元素本身时才会触发事件处理器-->
<div @click.self="doThat">...</div>
<!-- 阻止对元素本身的点击事件的默认行为 -->
<div @click.self.prevent></div>
<!-- 添加事件监听器时,使用 capture 变为【捕获】模式 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 立即发生滚动事件的默认行为 ,而非等待 onScroll 完成 -->
<div @scroll.passive="onScroll">...</div>
请勿同时使用 .passive 和 .prevent,因为 .passive 已经向浏览器表明了你不想阻止事件的默认行为。 .passive 修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。
- 按键修饰符: .enter/.tab/.delete (捕获“Delete”和“Backspace”两个按键)/.esc/.space/.up/.down/.left/.right
<!-- 仅在 key 为 Enter 时调用 submit -->
<input @keyup.enter="submit" />
<!-- $event.key 为 'PageDown' 时调用事件处理 -->
<input @keyup.page-down="onPageDown" />
- 系统按键修饰符:
.ctrl/.alt/.shift/.meta
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>
- .exact 修饰符: .exact 修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>
- 鼠标按键修饰符: .left/.right/.middle 这些修饰符将处理程序限定为由特定鼠标按键触发的事件
9、 表单输入绑定 v-model
-
v-model 指令:
<input :value="text" @input="event => text = event.target.value"><input v-model="text">(简写)
-
文本类型的
<input>和<textarea>文本域元素会绑定value属性并侦听input事件; -
单选框还复选框会绑定 checked 属性并侦听 change 事件。
-
<select>下拉列表 会绑定 value属性并侦听 change 事件。
v-model会忽略任何表单元素上的初始值。你应该【使用 data() 选项来声明该初始值】。 white-space: pre-line;可以保留textarea里面的格式,包括空格、回车、tab、换行
- `<input type="checkbox" id="checkbox" v-model="checked" />`
- `<label for="checkbox">{{ checked }}</label>`
- input要有id,label要for上边的id。这样才会有联动效果 。
-
将多个复选框绑定到同一个数组或集合的值:
data() {return {checkedNames: [] } } -
<option disabled value="">Please select one</option>- select建议提供一个空值的禁用选项,如上面所示.否则在 iOS 上用户无法选择第一项
-
<select v-model="selected"><option v-for="({text,value},index) in options" :key="index" :value="value">{{text}}</option></select>- value动态绑定
-
多选 (值绑定到一个数组) 用multiple
picked 在被选择时是字符串 "a"
<input type="radio" v-model="picked" value="a" />
toggle 只会为 true 或 false
<input type="checkbox" v-model="toggle" />
toggle 只会为 yes 或 no
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no" />true-value 和 false-value 是 Vue 特有的 attributes,仅支持和 v-model 配套使用。也可以将其绑定为其他动态值
selected 在第一项被选中时为字符串 "abc"
<select v-model="selected"><option value="abc">ABC</option></select>
- 修饰符:
.lazy/.number/.trim
<input v-model.lazy="msg" /><input v-model.number="age" /><input v-model.trim="msg" />
10、生命周期 created()、mounted()、unmounted()、updated()
- berforCreate() / created()、创建前回调 / 创建后回调(回调即为钩子)
- berforMount() / mounted()、挂载
- berforUpdate() / updated()、更新
- berforUnmount() / unmounted()、卸载
首次渲染叫挂载,挂载完毕发起ajax拿到数据再渲染。避免用箭头函数来定义生命周期钩子
11、侦听器watch:{监听data某数据}/deep、immediate、flush /this.$watch()
-
之前计算属性(computed)时一手数据变化,二手数据不应该有一些“副作用”出现。但是如果非要有副作用就用侦听器watch。
-
watch中和一手数据的一个名字同名,就说明在监听一手数据的变化,如果变化了,这个函数就会被调用。仅当数据源变化时,才会执行回调
-
浅层侦听:
watch: {
// 每当 question 改变时,这个函数就会执行
question(newQuestion, oldQuestion) {
if (newQuestion.includes('?')) {
this.getAnswer() //在方法中定义一个getAnswer方法
} } }
-
computed和watch的区别:
- 都是监听监听一手数据的变化,但是computed定位是专注于计算二手数据而不带来副作用 ,而使用watch是为了计算二手数据时带来副作用的。
- watch 默认是浅层的:被侦听的属性,仅在被赋新值时,才会触发回调函数——而嵌套属性的变化不会触发。如果想侦听所有嵌套的变更,你需要深层侦听器。
-
深层侦听: 深度侦听需要遍历被侦听对象中的所有嵌套的属性。必要时才使用它
watch: {
question: {
handler(newValue, oldValue) {
},
deep: true
}
}
-
即时回调的侦听器: 创建侦听器时,立即执行一遍回调,就使用
immediate: true -
回调的触发时机: 如果想在侦听器回调中能访问被 Vue 更新之后的DOM,你需要指明 `flush: 'post'
响应式 是代理 能侦听到嵌套数据,而watch想侦听到嵌套,需要加上
deep:true。但是拿不到老数据,因为数据是个地址,修改后直接地址里的也修改了,想要拿到老数据,要加immediate:true挂载时立即先触发一下拿到原始值
- 使用组件实例的 $watch() 方法来命令式地创建一个侦听器.还可以停止侦听器
<div id="app">
<p>
问一个问题:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
<hr />
<button @click="toggleWatch">侦听/不侦听</button>
</div>
<script type="module">
import { createApp } from "vue";
const root = {
data() {
return {
question: "请提问...",
answer: "默认答案",
watching: false, //是否侦听的状态
unwatch: null
};
},
methods: {
getAnswer() {
this.answer = Math.random() > 0.5 ? "yes" : "no";
},
toggleWatch() {
if (!this.watching) { // 如果没有侦听 ,就建立侦听unwatch。
this.unwatch = this.$watch("question", (nv,ov)=>{ //想this一样用箭头函数
console.log("question changed",nv,ov);
});
this.watching = true //侦听状态改为true
}else{
this.unwatch() //取消侦听
this.watching = false //侦听状态改为false
this.unwatch = null // 清空侦听的调用函数
}
},
},
};
createApp(root).mount("#app");
this.$watch()。叫$的全都是选项。this是当前实例
- 停止侦听器:设置侦听器会返回一个
unwatch函数,代表取消侦听
12、模板引用 ref expose
mounted() { this.$refs.input.focus()}
<input ref="input" />
只可以在【组件挂载后】才能访问模板引用 .ref 数组并不保证与源数组相同的顺序
-
访问模板的引用【this.$refs.input】获取ref 的元素。以方便对元素的使用
<input :ref="(el) => el.focus()">挂载完成后回调ref定义的函数
-
expose 用于限制对子组件实例的访问 expose: ['publicData', 'publicMethod'] 父组件通过模板引用访问到子组件实例后,仅能访问 publicData 和 publicMethod
13、组件基础 components:{} props emits slot标签 component标签 动态组件切换案例
-
定义一个组件 一个单独的.vue文件 就是单文件组件(SFC)
-
使用组件(单独注册)
- 子组件导出 export default{}
- 父组件导入子组件import SonCom from './SonCom.vue'
- 父组件需在components选项上注册它以后才能部署它。components: {SonCom}
- 注册它以后才能部署它
<SonCom />。可多次部署,互不影响,因为每使用一个子组件就创建了个新的实例。 - 子组件命名大驼峰
- 如果是在 DOM 中书写该模板用烤串
<son-com></son-com>
-
传递 props: props 将任何数据传递给组件:
- 父组件:声明 :list = todos 通过指令把todos的数据携带给子组件
- 子组件:配合 props:["list"] 接收到父组件的数据,同时可以直接使用该数据
props是专门接收父组件给注入的属性。父组件部署子组件并把数据传递给子组件。
-
监听事件 emits
-
父组件data添加 postFontSize:1 数据属性控制文章的字体大小 (非固定单词)
-
<div :style="{ fontSize: postFontSize + 'em' }">1234</div>- 子组件:声明 emits:["add"] 用自定义事件和父组件通信emits
- 父组件:接收 @add = "callback" 接收并用一个函数来监听处理这个自定义事件
-
-
通过插槽来分配内容: slot标签
- 父组件使用子组件注册部署
- 子组件使用作为一个占位符,父组件传递进来的内容就会渲染在这里
-
动态组件 component标签
- currentTab 改变时组件也改变
<component :is="currentTab"></component>被传给 :is 的值可以是以下几种:被注册的组件名 或 导入的组件对象
- currentTab 改变时组件也改变
当使用
<component :is="...">来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过<KeepAlive>组件强制被切换掉的组件仍然保持“存活”的状态。
- DOM 模板解析注意事项
- 大小写区分: 在脚本架中可以使用大驼峰,烤串,单标签双标签 在html中只能使用全小写或中划线,双标签
- 闭合标签:在DOM中要显式地写出关闭标签。除特殊标签img等
- 元素位置限制:
<ul>,<ol>,<table> 和 <select>,相应的,某些元素仅在放置于特定元素中时才会显示,使用is属性解决<table> <tr is="vue:blog-post-row"></tr> </table>
-----------------------
深入组件
1、 注册 app.component()
-
注册全局组件,可在任意组件的模板中使用 app.component('SonComponent',{ /* 组件的实现data,method... */})
-
使用单文件组件,可以注册被导入的 .vue 文件 。也可以被链式调用 import MyComponent from './App.vue' app.component('Son', Son) .component('SonB', SonB)
-
全局注册和局部注册区别: 全局注册,没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”) 会使项目的依赖关系变得不那么明确,影响应用长期的可维护性。
使用 PascalCase 作为组件名的注册格式
2、 Props(父子通信) 父给子数据
- 父组件:声明 :list = todos 通过指令把todos的数据携带给子组件
- 子组件:配合 props:["list"] 接收到父组件的数据,同时可以直接使用该数据
-
普通声明: props: ['foo']
-
对象形式声明: 对于以对象形式声明中的每个属性,key 是 prop 的名称,而值则是该 prop 预期类型的构造函数。如下:title必须是字符串类型,likes必须为数字类型。
-
props: { title: String, likes: Number } 即 props校验
-
使用一个对象绑定多个 prop
data() {
return {
post: {
id: 1,
title: 'My Journey with Vue'
}
}
}
<BlogPost v-bind="post" /> 相当于:
<BlogPost :id="post.id" :title="post.title" />
- 单向数据流: props要遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。不允许子组件修改父组件的数据
如果想要局部使用这个数据,最好是新定义一个局部数据属性,从 props 上获取初始值即可: props: ['initialCounter'], data() {return {counter: this.initialCounter } } 需要对传入的 prop 值做进一步的转换 ,最好是基于该 prop 值定义一个计算属性:
props: ['size'],
computed: {
// 该 prop 变更时计算属性也会自动更新
normalizedSize() {
return this.size.trim().toLowerCase()
}
}
- Prop 校验
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'
}
}
}
}
required: true 必传 default:默认
- 运行时类型检查 String、Number、Boolean、Array、Object、Date、Function、Symbol
也可以这样匹配:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
props: { author: Person }
Vue 将会通过 instanceof 来检查类型是否匹配。这里vue会通过instanceof Person 来校验 author prop 的值是否是 Person 类的一个实例。
3、 事件 $emit() v-model (重)
- 触发与监听事件
- 子组件:声明 emits:["someEvent"] 用自定义事件和父组件通信emits
- 父组件:接收 @some-event = "callback" 接收并用一个函数来监听处理这个自定义事件
- 组件的事件监听器也可支持.once修饰符: @add.once = "callback"
模板中推荐使用 kebab-case 形式来编写监听器
-
事件参数
<button @click="$emit('increaseBy', 1)"></button>所有传入$emit()的额外参数都会被直接传向监听器。举例来说,$emit('foo', 1, 2, 3)触发后,监听器函数将会收到这三个参数值。 -
声明触发的事件
- 显式语法 emits 选项声明 emits: ['inFocus', 'submit']
- 对象语法声明:
emits: {submit(payload) {通过返回值为
true还是为false来判断验证是否通过 } }
-
事件校验
emits: {
// click事件的校验器为空=不做校验
click: null,
/* 载荷同时包含email和password才算合法 */
submit: ({ email, password }) => {
if (email && password) {
return true // 自定义事件校验通过
} else {
console.warn('submit事件的载荷必须携带email和password')
return false // 事件校验失败
}
}
},
methods: {
submitForm(email, password) {
this.$emit('submit', { email, password })
}
}
-
配合 v-model 使用 首先它是语法糖,一行顶两行 。参数 。修饰符
- 想要拿到父组件的数据,子组件必须声明props:["modelValue"]
- 父组件:modelValue=datas , 子组件: props:["modelValue"]
- 想要同步的反向回去,必须有自定义事件声明emits:["update:modelValue"]
- 子:emits:["update:modelValue"],父: @update:modelValue = callback
- 父组件:注册子组件,使用
<Son v-model="searchText" @update:modelValue=callback></Son> - 子组件:声明props,emits。点击可以使用emit把数据给父,输入也可以把输入内容反向给父
- 想要拿到父组件的数据,子组件必须声明props:["modelValue"]
-
input上的v-model:以下三种写法等同
-
v-model="searchText" -
:value="searchText" @input=searchText=$event.target.value -
:value="searchText" @input=e=>text=e.target.value
-
-事件中的v-model:
v-model="searchText"
相当于
:modelValue="searchText" @update:modelValue="newValue => searchText = newValue"
- 带参数的v-model: (相当于把modelValue替换成title)
v-model:title="bookTitle"相当于:value="title" @input="$emit('update:title', $event.target.value)"(改名要重新声明props:["title"] , emits:["update:title"])
4、 透传Attributes $attrs
传递给一个组件,却没有被该组件声明为 props 或 emits 的 属性或者 v-on 事件监听器。例:class、style 和 id
-
透到根元素: 当一个组件以单个元素为根作渲染时,透传的 属性 会自动被添加到根元素上
-
合并: 一个子组件的根元素已经有了 class 或 style 属性,它会和从父组件上继承的值合并
-
禁用继承 inheritAttrs: false。: 如果你不想要一个组件自动地继承 属性,你可以在组件选项中设置 inheritAttrs: false。
-
$attrs访问 透传的属性 透传进来的 属性 可以在模板的表达式中直接用$attrs访问到。$attrs对象包含了除组件所声明的 props 和 emits 之外的所有其他 属性
foo-bar 这样的一个属性需要通过 $attrs['foo-bar'] 来访问。
@click这样的v-on事件监听器,在 $attrs 对象下被暴露为一个函数$attrs.onClick
-
v-bind="$attrs" 给想要拿到样式的元素添加 并且需要 inheritAttrs: false
-
多根节点的继承 需要显式让那个根节点绑定
$attrs
5、 插槽 Slots : v-slot(#)
- 父元素传入插槽内容 ,子元素用插槽
<slot></slot>接收渲染 - 也可为插槽设置默认内容
<slot> <!-- 默认内容 --> </slot>
插槽内容可以访问到父组件的数据作用域,但是无法访问子组件的数据
-
具名插槽 v-slot 简写 #
- 每一块内容用template包裹 。不写名字的就是默认插槽
v-slot:name="slotProps"==>#name="slotProps"slotProps是子组件在插槽上暴漏出来的数据
-
子获取父的数据
- 父组件节点
v-slot:header简写#header - 子组件根据需要通过name接收
name="header"
- 父组件节点
-
父获取子的数据:作用域插槽
- 父组件v-slot="b"(名字随便)
<Son v-slot="b">{{b.text}}{{b.count}}</Son>b(通常叫slotProps)也可以解构为{text,count} => v-slot="{ text, count }" - 子组件的数据 msg。
<slot :text="msg" :count="1"></slot>
- 父组件v-slot="b"(名字随便)
6、 依赖注入(祖孙通信) provide、inject(深层级子拿数据)、computed(vue3全局函数)、Symbol
-
提供数据 provide
- 对于 provide 对象上的每个属性,后代组件会用其 key 为注入名查找期望注入的值,属性的值就是要提供的数据。
- 使用provide选项:
provide: { message: 'hello!'}- 如需依赖当前组件实例的状态(无响应式) data(){return{message: 'hello!'} }
- 使用provide函数:
provide() { return { message: this.message } } - 应用层 Provide:
-
app.provide(注入名(message), 值('hello!'))
-
注入数据 inject
- 要拿上层组件的数据,使用inject选项
inject: ['message'], 在data中也可以通过this.message访问到注入数据 - 别名注入
inject: { 本地属性名(locamsg): {from: 注入来源名('message')} } - 注入默认值:
- 要拿上层组件的数据,使用inject选项
// 当声明注入的默认值时
// 必须使用对象形式
inject: {
message: {
from: 'message', // 当与原注入名同名时,这个属性是可选的
default: 'default value' //一般用回调函数。不把值写死
},
user: {
// 对于引用数据类型。需要独立数据的,请使用工厂函数
default: () => ({ name: 'John' })
}
}
inject: {
message: {
from: 'message',
default: () => ({ name: 'John' })
}
}
- 工厂函数:作为实例方法,为所有实例共享,大家调用的是同一个地址的函数,返回值是一个对象。是一个闭包函数。调用形成一个闭包,修改不影响其他调用的内容
- 和响应式数据配合使用 computed() 想要透传自己的数据,又想要响应式时使用
import { computed } from 'vue'
export default {
data() {
return {
message: 'hello!'
}
},
provide() {
return {
*显式提供一个计算属性,一手数据变化,重新计算,后代的数据就有响应式了*
*没有这个computed函数的话,父的一手数据变化,子组件的不会变化*
message: computed(() => this.message)
}
}
}
上面的用例需要设置 app.config.unwrapInjectedRef = true 以保证注入会自动解包这个计算属性
- 使用 Symbol 作注入名 大型项目中使用
推荐在一个单独的文件中导出这些注入名 Symbol:
// keys.js
export const myInjectionKey = Symbol()
// 在供给方组件中
import { myInjectionKey } from './keys.js'
export default {
provide() {
return {
[myInjectionKey]: {
/* 要提供的数据 */
}
}
}
}
// 注入方组件
import { myInjectionKey } from './keys.js'
export default {
inject: {
injected: { from: myInjectionKey }
}
}
7、 异步组件 性能优化 defineAsyncComponent(全局)
用到才导入使用。导入完毕才履约
- 第一种
import { defineAsyncComponent } from 'vue'
import SonCom from "./SonCom.vue" //子组件。这种是同步导入,会阻塞下边
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
resolve(获取到的组件 SonCom) //这是异步
})
})
// ... 像使用其他一般组件一样使用 AsyncComp(子组件异步后的名字)
components:{AsyncComp} //注册子组件。模板中使用<AsyncComp/>
- 第二种:
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./SonCom.vue') // es模块动态异步导入,返回也是一个promise
)
- 第三种:全局注册
app.component('SonCom', defineAsyncComponent(() =>
import('./SonCom.vue')
))
- 第四种:局部注册
import { defineAsyncComponent } from 'vue'
export default {
components: {
AdminPage: defineAsyncComponent(() =>import('./SonCom.vue'))
}
}
- 加载与错误状态:
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./SonCom.vue'),
// 加载异步组件时使用的组件(可以放转圈的图等)
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200, //加载完成快,加载组件和最终组件之间的替换太快产生闪烁
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
8、通信方法:父子通讯props,子父通讯emit,子孙通讯provide-inject,暴漏ref-expose。vueX
-----------------------
程序最重要的三个点:(进阶目标)(项目亮点) 逻辑复用,性能优化,工程化 hook 钩子,回调
逻辑复用
1、 组合式函数 setup()入口函数,ref,reactive和toRefs,computed(),watch()(需要从vue中导入),hook(重)
- vue2中的生命周期都是叫做选项,不能复用,其他地方想用需搬砖。
- vue3种一切代码都写在setup入口函数中,他没有创建 create 的生命周期。setup 就相当于 beforecreate 和 created,也是生命周期。
- setup的参数:props,context
- setup(props,{emit,slots,attrs,expose}){}使用的数据都需要暴漏出来
setup,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted(需要从vue中导入)
-
ref=值响应式 ref相当于一个地址:
- 导入引用ref :
import { ref } from "vue"; - 申请一个内存地址 用于存储年龄 并填充初始值为20:
const ageRef = ref(20); - 将响应式数据暴露给模板:
return {geRef};
- 导入引用ref :
-
在代码中访问地址的值必须geRef
.value。 模板中直接访问地址中的值geRef -
reactive=对象响应式 toRef=从响对中分离响值 toRefs=将响对全分离为响值ref
- 导入引用ref,toRef,toRefs,reactive :
import { ref,toRef,toRefs,reactive } from "vue"; - 申请一个对象响应式 用于存储对象
const state = reactive({ name: "jcjc",age: 18}) - 将响应式数据打散成一堆ref暴露给模板:
return {...toRefs(state)}
- 导入引用ref,toRef,toRefs,reactive :
-
ref储存元素或组件:
- 申请一个地址 填充为Null
const elemRef = ref(null); - 将响应式数据暴露给模板:
return {elemRef}; - 将P元素丢入指定的地址 ref。挂载完毕以后通过.value设置elemRef这个DOM
<p ref="elemRef">我是一行文字</p>
- 申请一个地址 填充为Null
-
computed计算属性函数
- 导入引用computed:
import { computed} from "vue"; - 通过一手数据改变,调用函数,让二手数据也改变
computed(()=>{state.year++})
- 导入引用computed:
-
watch侦听函数
- watch(要侦听的数据项 ,数据变化回调(n,o)=>{},配置项{immediate,deep})
- 要侦听的数据项:基础数据类型直接用ref地址,对象的用回调函数()=>state.wife
- 单个监听
watch(ageRef,(newValue, oldValue) => {console.log(newValue, oldValue)}, {immediate:true}; - 深度监听
watch(()=>state.wife,(newValue, oldValue) => {console.log(newValue, oldValue)}, {immediate:true,deep:ture}
-
watchEffext 自动收集依赖项(要侦听的一手数据项)
- 里边写了谁就侦听谁,不需要直接写出侦听项。在创建实例时就会收集好,挂载之初
watchEffext(()=>{console.log(state.age)}) - 对于对象重新赋值了地址改变了才会侦听,地址例的东西增删改查但是地址不变就无法侦听到,也就是无法实现深度侦听,
- 里边写了谁就侦听谁,不需要直接写出侦听项。在创建实例时就会收集好,挂载之初
-
自定义hook:命名:usexxx 就是一个自带变化逻辑的响应式数据的工厂函数,usexxx一调用,还可以带入参,直接得到响应式数据。或者说它就是一个响应式数据的生成器,自带变化逻辑,可以在生命周期有变化逻辑,也可以在watch,computed有一个变化逻辑,都无所谓,最后把响应式数据return出来,用的人一行代码根据自己业务逻辑给参数然后配置直接得到响应式数据使用
-
鼠标位置hook
- mouuseHook.js:
import { reactive, toRefs, onMounted, onUnmounted } from "vue"
function useMousePosition(){
const state = reactive({
x: 0,
y: 0
})
const mousemoveHandler=(e) => {
state.x = e.pageX
state.y = e.pageY
}
onMounted(()=> {
window.addEventListener('mousemove', mousemoveHandler)
console.log("挂载完毕");
})
onUnmounted(() => {
window.removeEventListener('mousemove', mousemoveHandler)
console.log("事件监听已移除");
})
return toRefs(state)
}
export default useMousePosition
- 子组件:要用usexxx导入
<template> <p>x:{{ x }} y:{{ y }}</p> </template>
import useMouuseHook from './mouuseHook.js';
export default {
setup() {
const { x, y } = useMouuseHook()
return { x, y }
}
}
- app.vue:
<Reactive v-if="flag"></Reactive>
<button @click="changeFlagHandler">组件切换</button>
omponents: { Reactive },
data() {
return {
flag: true,
};
},
methods: {
changeFlagHandler() {
this.flag = !this.flag;
console.log("组件已切换");
},
},
- 倒计时hook 子组件: 据:{{targetDate}}还有:{{day}}天:{{hour}}时:{{minute}}分:{{second}}秒
import useCountDown from "./useCountDown"
import {ref} from "vue"
export default {
setup() {
const targetDate = ref(new Date(2022, 12, 1));
return {
targetDate,
...useCountDown(targetDate.value)
}
}
}
useCountDown.js
function timeDiffer(nowTime, nationalDay) {
//获取时间戳差值
var timeDifS = nationalDay - nowTime
//获取天数
var days = parseInt(timeDifS / (24 * 60 * 60 * 1000))
//获取小时数
var timeDDifs = timeDifS % (24 * 60 * 60 * 1000)
var hours = parseInt(timeDDifs / (60 * 60 * 1000))
//获取分钟
var timeHDifs = timeDDifs % (60 * 60 * 1000)
var minutes = parseInt(timeHDifs / (60 * 1000))
//获取秒
var timeSDifs = timeHDifs % (60 * 1000)
var seconds = parseInt(timeSDifs / (1000))
//获取毫秒
var msecond = timeSDifs % 1000
//显示
// var timeDiff = `${days}天零${hours}小时${minutes}分${second}秒`
return { days, hours, minutes, seconds }
}
import { reactive, onMounted, onUnmounted, toRefs } from "vue"
function useCountDown(targetDate) {
const state = reactive({
day: 0, hour: 0, minute: 0, second: 0
})
let timer = null;
onMounted(() => {
timer = setInterval(() => {
const { days, hours, minutes, seconds } =
timeDiffer(new Date(), targetDate);
state.day = days;
state.hour = hours;
state.minute = minutes;
state.second = seconds;
}, 1000);
});
/* 组件卸载时移除定时器 */
onUnmounted(() => {
if (timer) {
clearInterval(timer);
console.log("timer已移除");
}
});
return toRefs(state)
}
export default useCountDown
-
scriptSetup
<script setup></script>- 所有代码相当于直接写在setup函数中 ,位于代码顶层的所有变量和函数都直接对模板暴露
- 组件导入以后就可以直接使用不需要再component了,
- 不需要再return了,直接以下就可使用:
const xusui = computed(() => age.value + 1);
-
provide-inject(全局)
- 需从vue导入这两个函数。使用的时候都有两个入参
provide("注入名",{值})inject("注入名",默认值)- 使用参数:
- 导入定义props,和定义emits,
import { defineProps, defineEmits,defineExpose } from "vue";
- 导入定义props,和定义emits,
- 定义属性
const props = defineProps({
name: String,
age: Number,
});
- 定义事件(两种方式)
1、const emit = defineEmits(["change", "delete"]);
2、定义自定义事件时,返回值就是发送自定义事件的方法函数
function deleteHandler() {
emit("delete", "aaa");
}
<p>name: {{ props.name }}, age: {{ props.age }}</p>
<button @click="$emit('change', 'bbb')">change</button>
<button @click="deleteHandler">delete</button>
- defineExpose暴漏数据 // 向外界暴露数据
defineExpose({
a,
b,
sayHello
});
2、 自定义指令 directives:{自定义指令名}
只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用自定义指令。建议单独做个文件夹放里边,要记得导出 ,导入使用
局部注册指令:
- 定义自定义的指令做的事情:
当一个 input 元素被 Vue 插入到 DOM 中后,它会被自动聚焦:
const focus = { mounted: (el) => el.focus() } - 注册绑定指令:
directives:{自定义指令名}
全局注册指令:(推荐全局注册)
const app = createApp({})
app.directive('focus', {mounted: (el) => el.focus()})
- 指令钩子 和生命周期钩子近乎同步
- el当前元素,可用于直接操作DOM
- binding对象{value,oldValue,arg,modifiers,instance,dir}
- value:(传递给指令的值[=],如v-mydirective = "1 + 1"中,值是2),
- arg:(传递给指令的参数[:],如v-mydirective : foo 中,参数是foo)
- modifiers:修饰符,如v-my-directive.foo.bar 中,修饰符是foo和bar
- 除了 el 外,其他参数都是只读的
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}
- 自定义指令仅需在 mounted 和 updated 上实现相同的行为
<div v-color="red"></div> //自己给值(binding.value)
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
-
binding.value也可以设置成一个对象使用
-
自定义指令在组件上使用也会透传给组件的根节点
-
小案例:
app.directive("pin", (el, binding) => {
el.style.position = "fixed";
let arg = binding.arg || "top";
el.style[arg] = binding.value+"px"
})
<p v-pin="'20'">我在上边</p>
<p v-pin:bottom="'10'">我在下边</p>
- 切换组件(补充):
<Son v-if="flag"></Son>
<button @click="changeFlagHandler">组件切换</button>
let flag = ref(true);
const changeFlagHandler = () => {
flag.value = !flag.value
console.log("组件已切换",flag);
}
3、 插件 install()
- 使用:
app.use(插件(myPlugin), { 配置如英汉词典一本 })
const myPlugin = {
install(app, options) {
代码
}
}
-
app:是一个对象
-
app.config.globalProperties :app配置里的全局属性
-
options:是use时的配置内容
-
小案例:
<h1>{{ $translate('greetings.hello') }}</h1>
// plugins/i18n.js
export default {
install: (app, options) => {
// 注入一个全局可用的 $translate() 方法,这样所有的组件都有这个方法了
app.config.globalProperties.$translate = (key) => {
// 获取 `options` 对象的深层属性
// 使用 `key` 作为索引
return key.split('.').reduce((o, i) => {
if (o) return o[i]
}, options)
}
app.provide('i18n', options)
}
}
import i18nPlugin from './plugins/i18n'
app.use(i18nPlugin, {
greetings: {
hello: 'Bonjour!'
}
})
子组件:
export default {
inject: ['i18n'],
created() {
console.log(this.i18n.greetings.hello)
}
}
- 插件里可以使用的:
- 1.通过
app.component()和app.directive()注册一到多个全局组件或自定义指令。 - 2.通过
app.provide()使一个资源可被注入进整个应用。 - 3.向
app.config.globalProperties中添加一些全局实例属性或方法 - 4.一个可能上述三种都包含了的功能库 (例如 vue-router)。 如:可以把自定义指令也放进插件里用
- 1.通过
-----------------------
内置组件
1、 Transition
2、 TransitionGroup
3、 KeepAlive
多个组件间动态切换时缓存被移除的组件实例
- 用
<KeepAlive>内置组件将这些动态组件包装起来 在 DOM 模板中使用时,它应该被写为<keep-alive>
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
components: { CompA, CompB },
data() {
return {
current: 'CompA'
}
}
-
include 和 exclude 包含/排除
子组件要声明name选项才能使用:name:"CompA", -:include="['CompA', 'CompB']"只缓存组件a和b,其他的不缓存:exclude="['CompA', 'CompB']"除了组件a和b不缓存,其他的组件都缓存
-
:max="10" 最大缓存实例数
LRU 缓存:如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁 -
缓存实例的生命周期 activated(被激活) 和 deactivated(失活) 子组件中使用,可以查看组件是否被激活
4、 Teleport
传送门:把一段DOM元素送到指定位置(常用弹出层)
<button @click="open = true">Open Modal</button>
<Teleport to="body"> //点击时把下边DOM送到body标签下
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button> //点击时关闭
</div>
</Teleport>
-
禁用 Teleport 在桌面端和移动端来动态传入ture或false 处理这两种不同情况
<Teleport :disabled="isMobile">...</Teleport> -
多个 Teleport 共享目标 多个组件挂载同一个目标元素上,按顺序依次执行
5、 Suspense
-----------------------
应用规模化
1、 单文件组件
2、 工具链
3、 路由
4、 状态管理
5、 测试
6、 服务端渲染(SSR)
-----------------------
最佳实践
1、 生产部署
2、 性能优化
3、 无障碍访问
4、 安全
-----------------------
TypeScript
1、 总览
2、 TS与组合式API
3、 TS与选项式API
-----------------------
进阶主题