mixin
Mixin 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个 mixin 对象可以包含任意组件选项。当组件使用 mixin 对象时,所有 mixin 对象的选项将被“混合”进入该组件本身的选项。
mixin会和原本组件的选项里的数据合并(例如data,methods之类),组件的选项优先级高于mixin,如果有相同的会被替换,如果是生命周期的钩子函数,会新执行mixin再执行组件里的钩子函数
<!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>
<script src="js/vue.js"></script>
</head>
<body>
<div id ="app1">
</div>
<p>--------</p>
<div id ="app2">
</div>
<script>
const myMixin={
data() {
return{
msg:'hello'
}
},
methods: {
hello(){
alert('mehhh')
}
},
mounted () {
console.log('先加载mixin')
},
}
const app1 = Vue.createApp({
data(){
return{
msg:"组件里优先级更高“
site:"kkk.com"
}
},
methods:{
change(){
alert('哈哈')
}
},
mixins: [myMixin],
template:`
<div>
<h1>{{msg}}</h1>
<h1>{{site}}</h1>
<button @click="hello">点我</button>
<button @click="change">改变</button>
</div>
`,
mounted () {
console.log('后加载组件的钩子函数')
},
});
app1.mount('#app1');
const app2 = Vue.createApp({
mixins: [myMixin],
template:`
<div>
<h1>{{msg}}</h1>
<button @click="hello">点我</button>
</div>
`
});
app2.mount('#app2');
</script>
</body>
</html>
site是合并的,msg取优先级更高的,钩子函数先后执行
当mixin里为自定义属性时,不能直接引用,组件挂载最终是再options上通过options进行获取 但是如果有相同属性会被覆盖,可以通过配置改变,也可全局配置mixin
app1.config.optionMergeStrategies.age = (mixinVal, appValue) => {
return mixinVal || appValue;
};
app1.mount("#app1");
app1.mixin({
});
<body>
<div id="app1"></div>
<script>
const myMixin = {
age: 100,
};
const app1 = Vue.createApp({
mounted () {
console.log(this.$options);
},
data() {
return {
msg: "hhh",
};
},
methods: {
hello() {
alert("哈哈");
},
},
age:99,
mixins: [myMixin],
template: `
<div>
<h1>{{msg}}</h1>
<h1>{{age}}</h1>
直接引用mixin的自定义属性是不会出现的,需要运用到this.$options,
<h1>{{this.$options.age}}</h1>
<button @click="hello">点我</button>
</div>
`,
});
app1.config.optionMergeStrategies.age = (mixinVal, appValue) => {
return mixinVal || appValue;
};
app1.mount("#app1");
app1.mixin({
});
</script>
</body>
自定义指令
自定义全局指令
app.directive('focus', {
mounted (el) {
el.focus();
}
});
<body>
<div id="app"></div>
<script>
const app = Vue.createApp({
data() {
return {
msg: "hello",
};
},
template: `
<div>
<input placeholder="请输入姓名" v-focus>
</div>
`,
});
app.directive('focus', {
mounted (el) {
el.focus();
}
});
app.mount("#app");
</script>
</body>
自定义局部指令
使用directives: lkDirective, 引入
<body>
<div id="app"></div>
<script>
const lkDirective = {
focus: {
mounted(el) {
el.focus();
},
},
};
const app = Vue.createApp({
data() {
return {
msg: "hello",
};
},
template: `
<div>
<input placeholder="请输入姓名" v-focus>
</div>
`,
directives: lkDirective,
});
app.mount("#app");
</script>
</body>
自定义指令的钩子函数
<body>
<div id ="app">
</div>
<script>
const lkDirective ={
focus:{
created () {
console.log('created')
},
beforeMount () {
console.log('beforeMount')
},
mounted (el) {
el.focus();
console.log('mounted')
},
beforeUpdate () {
console.log('beforeUpdate')
},
updated () {
console.log('updated')
},
beforeMount(){
console.log('beforeMount')
},
unmounted(){
console.log('unmounted')
}
}
};
const app = Vue.createApp({
data() {
return{
isshow:true
}
},
template:`
<div v-show="isshow">
<input type="text" v-focus>
</div>
`,
directives: lkDirective,
}).mount('#app');
</script>
</body>
v-show改为v-if可以触发两个mounted函数
自定义指令传参
<body>
<div id ="app">
</div>
<script>
const app = Vue.createApp({
data() {
return{
posData:{
top:200,
left:200
}
}
},
template:`
<div class="box" v-fixed:pos="posData">哈哈哈</div>
`,
methods: {},
});
app.directive('fixed', (el,binding)=>{
console.log(el,binding);
el.style.position="fixed";
el.style.top=binding.value.top+'px'
el.style.left=binding.value.left+'px'
});
app.mount('#app');
</script>
</body>
通过传递参数,调用value进行运算
Teleport 传送门
当有多层嵌套组件时,有时会出现嵌套子组件,执行后要展示在例如最外层的父组件上,这时就可以使用teleport
<!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>
<script src="js/vue.js"></script>
</head>
<style>
.box {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 500px;
height: 500px;
background-color: red;
}
.mask {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: #000;
opacity: 0.5;
display: flex;
justify-content: center;
align-items: center;
}
#root {
position: absolute;
width: 200px;
height: 200px;
background-color: #cccc;
}
</style>
<body>
<div id="app"></div>
<div id="root"></div>
<script>
const app = Vue.createApp({
data() {
return {
msg: "hello",
isshow: false,
};
},
methods: {
btnClick() {
this.isshow = !this.isshow;
},
},
template: `
<div class="box">
<button @click="btnClick">蒙版</button>
<!-- <div class="mask" v-show="isshow">{{msg}}</div> -->
<!-- <teleport to="body">
<div class="mask" v-show="isshow">{{msg}}</div>
</teleport>-->
<teleport to="#root">
<div class="mask" v-show="isshow">{{msg}}</div>
</teleport>
</div>
`,
}).mount("#app");
</script>
</body>
</html>
点击蒙版后,按照to属性的值,渲染到#root
optionsAPI 组合式api
(1)setup
setup不能调用生命周期或者选项内的东西,但反过来,生命周期或者选项可以调用setup中的东西, setup用来取代beforecreated和created两个钩子函数,可以定义data和一些methods的方法
<!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>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const app = Vue.createApp({
setup(props, context) {
return {
msg: "hello",
log: () => {
alert("kkk");
},
};
},
methods: {
deal(){
console.log(this.$options.setup())
},
},
template: `
<div>
<h1>{{msg}}</h1>
<button @click="log">dianw</button>
可以调用setup
<button @click="deal">处理</button>
</div>
`,
}).mount("#app");
</script>
</body>
</html>
(2)ref
ref:让基础类型的数据具备响应式 setup中不会调用生命周期里的选项内容,可以通过ref来实现响应 vue渲染的数据,可以通过控制台替换直接响应式修改显示在页面,使用setup后,修改了但没有响应 使用ref,实现动态响应,这里原理是改变proxy的值,所以settimeout的时候要修改的是value
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const app = Vue.createApp({
setup(props, context) {
const {ref} = Vue;
let msg =ref("hello")
setTimeout(() => {
msg.value="kkk"
}, 2000);
return {
msg
};
},
template: `
<div>
<h1>{{msg}}</h1>
<button @click="log">dianw</button>
</div>
`,
methods: {},
}).mount("#app");
</script>
</body>
(3)reactive
跟ref一样的功能,也是让setup中的数据实时响应
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const app = Vue.createApp({
setup(props, context) {
const {reactive} = Vue;
let pobj =reactive({name:'张三',sex:'男'});
let parr =reactive(['vue','react']);
setTimeout(()=>{
pobj.name="lily"
},2000)
return {
pobj,
parr
};
},
template: `
<div>
<h1>{{pobj.name}}{{pobj.sex}}</h1>
<h2>{{parr}}</h2>
</div>
`,
methods: {},
}).mount("#app");
</script>
</body>
(4)readonly的使用
常见的嵌套组件,数据流动都为单向,父组件的数据传递给子组件再到孙组件,为了避免数据改变, 可以使用readonly禁止修改
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const app = Vue.createApp({
setup(props, context) {
const {readonly} = Vue;
let pobj =readonly({name:'张三',sex:'男'});
setTimeout(()=>{
pobj.name="lily"
},2000)
return {
pobj,
};
},
template: `
<div>
<h1>{{pobj.name}}{{pobj.sex}}</h1>
</div>
`,
methods: {},
}).mount("#app");
</script>
</body>
两秒后数据不会发生变化,因为只读
(5)toRefs
对数据进行结构,可以显示,2秒后数据没有改变,不能动态响应,需要使用toRefs让解构的数据响应
<script>
const app = Vue.createApp({
setup(props, context) {
const { reactive } = Vue;
let pobj = reactive({ name: "张三", sex: "男" });
setTimeout(() => {
pobj.name = "lily";
}, 2000);
const { name, sex } = pobj;
return {
name,
sex,
};
},
template: `
<div>
<h1>{{name}}{{sex}}</h1>
</div>
`,
methods: {},
}).mount("#app");
</script>
使用toRefs 原理也是通过获取到proxy的value进行修改
<script>
const app = Vue.createApp({
setup(props, context) {
const { reactive,toRefs } = Vue;
let pobj = reactive({ name: "张三", sex: "男" });
setTimeout(() => {
pobj.name = "lily";
}, 2000);
const { name, sex } = toRefs(pobj);
return {
name,
sex,
};
},
template: `
<div>
<h1>{{name}}{{sex}}</h1>
</div>
`,
methods: {},
}).mount("#app");
</script>
加上toRefs后,两秒后结构的数据会变化
(6)toRef
例如有name、age、sex从父组件传递给子组件,但是父组件漏传了一个,子组件依然是使用的三个并改变了值,这时候会出现报错,例子如下
<script>
const app = Vue.createApp({
setup(props, context) {
const { reactive, toRefs } = Vue;
let pobj = reactive({ name: "张三", sex: "男" });
let { name, sex,age } = toRefs(pobj);
setTimeout(() => {
name.value = "lily";
sex.value = "女";
age.value=100;
}, 2000);
return {
name,
sex,
};
},
template: `
<div>
<h1>姓名:{{name}}性别:{{sex}}年龄:{{age}}</h1>
</div>
`,
methods: {},
}).mount("#app");
</script>
因为父组件没有传age过去,age属性未定义 让name、sex必传,age不必传,如果子组件使用了就使用toRef创建一个age,并设置值为100
<script>
const app = Vue.createApp({
setup(props, context) {
const { reactive, toRefs,toRef } = Vue;
let pobj = reactive({ name: "张三", sex: "男" });
let { name, sex } = toRefs(pobj);
let age = toRef(pobj,'age');
setTimeout(() => {
name.value = "lily";
sex.value = "女";
age.value=100;
}, 2000);
return {
name,
sex,
age
};
},
template: `
<div>
<h1>姓名:{{name}}性别:{{sex}}年龄:{{age}}</h1>
</div>
`,
methods: {},
}).mount("#app");
</script>
(7)context
传递给 setup 函数的第二个参数是 context。context 是一个普通 JavaScript 对象,暴露了其它可能在 setup 中有用的值: context有三个常用的,attrs,slots,emit
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
执行 setup 时,组件实例尚未被创建。无法访问data,computed,methods,refs (模板 ref),只能访问props、attrs、slots、emit 需要返回函数或者是数据都需要return才能被父组件访问
(8)计算属性新用法
<body>
<div id ="app">
</div>
<script>
const app = Vue.createApp({
setup(props,context){
const {ref,computed} = Vue;
let num1 =ref(10);
let num2=computed(()=>{
console.log('++++');
return num1.value*10
})
const add =()=>{
console.log('----come----');
num1.value +=10;
}
return{
num1,
num2,
add
}
},
template:`
<h2>{{num1}}</h2>
<h2>{{num2}}</h2>
<p>---------------</p>
<button @click="add">点我</button>
`
}).mount('#app');
</script>
</body>
在setup中运用computed属性,在setup执行后返回给页面渲染
computed中,可以传入对象,有get和set,可以分别获取和改变数据 这里使用setTimeout改变num2的值,通过计算属性的set,会自动计算num1对应的值
setup(props,context){
const {ref,computed} = Vue;
let num1 =ref(10);
let num2=computed({
get:()=>{
return num1.value*10
},
set:(res)=>{
console.log(res)
return num1.value=res/10;
}
});
setTimeout(()=>{
num2.value=2000;
},2000)
const add =()=>{
num1.value +=10;
}
return{
num1,
num2,
add
}
},
(9)watch侦听器
在setup中监听input的值
<body>
<div id="app"></div>
<script>
const app = Vue.createApp({
setup(props, context) {
const { ref, watch } = Vue;
let brand = ref("");
let site = ref("");
watch(
[brand, site],
([currentbrand, currentsite], [prebrand, presite]) => {
console.log("现在的", currentbrand, currentsite);
console.log("之前的", prebrand, presite);
}
);
return {
brand,
site,
};
},
template: `
<div>
品牌:<input v-model="brand">
<p>输入的品牌是{{brand}}</p>
</div>
<p>---------</p>
<div>
网站:<input v-model="site">
<p>输入的网站是{{site}}</p>
</div>
`,
}).mount("#app");
</script>
</body>
侦听响应式的引用类型数据
<body>
<div id="app"></div>
<script>
const app = Vue.createApp({
setup(props, context) {
const { ref, watch,reactive,toRefs } = Vue;
let eduObj =reactive({brand:'xxx学院',site:'www.com'})
watch(()=>eduObj.brand,(currentValue,preValue)=>{
console.log('现在的',currentValue)
console.log('之前的',preValue)
})
const {brand,site} =toRefs(eduObj)
return {
brand,
site,
};
},
template: `
<div>
品牌:<input v-model="brand">
<p>输入的品牌是{{brand}}</p>
</div>
<p>---------</p>
<div>
网站:<input v-model="site">
<p>输入的网站是{{site}}</p>
</div>
`,
}).mount("#app");
</script>
</body>
(10)watchEffect
跟watch的区别
<body>
<div id="app"></div>
<script>
const app = Vue.createApp({
setup(props, context,watchEffect) {
const { ref, watch } = Vue;
let brand = ref("");
let site = ref("");
watch(brand,(currentValue,preValue)=>{
console.log('现在的',currentValue)
console.log('之前的',preValue)
},{
immediate:false,
deep:true
})
watchEffect( () => {
console.log('开始侦听了');
console.log(brand.value);
console.log(site.value);
})
return {
brand,
site,
};
},
template: `
<div>
品牌:<input v-model="brand">
<p>输入的品牌是{{brand}}</p>
</div>
<p>---------</p>
<div>
网站:<input v-model="site">
<p>输入的网站是{{site}}</p>
</div>
`,
}).mount("#app");
</script>
</body>
(11)provide和inject
实际开发中,由于数据单向,孙组件要获取子组件的数据时,需要一层层传递,使用provide(发射),inject(订阅)可有效改善这个问题 组件进行发射,孙组件订阅,接受到发生的变量,不需要在经过父组件再一次传递
<body>
<div id="app"></div>
<script>
const lkSon = {
setup(){
const {inject} =Vue;
const bName = inject('brand','默认值')
const bCollege = inject('college')
return{
bName,
bCollege
}
},
template: `
<div style="width:200px;height:200px;background-color:green">
{{bName}}---{{bCollege}}
</div>
`,
};
const lkfather = {
components: {
"lk-son": lkSon,
},
template: `
<div style="width:400px;height:400px;background-color:red">
父组件
<lk-son></lk-son>
</div>
`,
};
const app = Vue.createApp({
setup() {
const { ref, reactive, provide } = Vue;
let brand = ref("hh");
let college = reactive({ city: "上海", site: "baidu.com" });
provide('brand',brand)
provide('college',college)
},
components: {
'lk-father':lkfather
},
template:`
<lk-father></lk-father>
`
}).mount("#app");
</script>
</body>
使用过程中要主要准许数据单向,数据在哪里就在哪里修改,可以通过传递的值进行readonly,或者是在最外层发射一个改变值的函数由孙组件接受
(12) 生命周期钩子新写法
setup中用法其实与平时的钩子函数用法差不多,只差别在名称 这里补充两个setup中使用的函数
页面刷新调用onRenderTracked 改变数据调用onRenderTriggered
<body>
<div id ="app">
</div>
<script>
const app = Vue.createApp({
setup() {
const {ref,onRenderTracked,onRenderTriggered} = Vue;
let msg =ref('kkk')
const handleClick=()=>{
msg.value='hhh'
}
onRenderTracked(()=>{
console.log('onRenderTracked')
})
onRenderTriggered(()=>{
console.log('onRenderTriggered')
})
return{
msg,
handleClick
}
},
methods: {},
template:`
<h1 @click="handleClick()">{{msg}}</h1>
`
}).mount('#app');
</script>
</body>
|