完整原文地址见简书 更多完整Vue笔记目录敬请见《前端 Web 笔记 汇总目录(Updating)》
ref()
**概念、原理 与 实战**reactive()
**概念、原理 与 实战**readonly
**限制对象的访问权限toRefs()
**对**reactive
**对象进一步封装使得相同的、相关的功能代码 可以比较 完整地聚合起来,
提高可维护性、可读性,提高开发效率;
规避 同一个功能的代码,
却散落在 组件定义中的**data、methods、computed、directives、template、mixin
**等各处 的问题;
--- Composition API 所有代码编写之前, 都要 建立在setup函数 之上; --- 在created 组件实例 被完全初始化之前 回调; (所以注意在**
setup
**函数中, 使用与**this
**相关的调用是没有用的) --- setup函数中的内容, 可以在 该组件的 模板**template
** 中直接使用; (如下例程)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
setup(props, context) {
return {
name: 'zhao',
handleClick: () => {
alert(666);
}
}
}
});
const vm = app.mount('#heheApp');
</script>
</html>
运行效果:
<script>
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
methods: {
test() {
console.log(this.$options);
}
},
mounted() {
this.test();
},
setup(props, context) {
return {
name: 'zhao',
handleClick: () => {
alert(666);
}
}
}
});
const vm = app.mount('#heheApp');
</script>
运行效果:
<script>
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
methods: {
test() {
console.log(this.$options.setup());
}
},
mounted() {
this.test();
},
setup(props, context) {
return {
name: 'zhao',
handleClick: () => {
alert(666);
}
}
}
});
const vm = app.mount('#heheApp');
</script>
如下,这样没有使用**ref
**/**reactive
**的写法,是不会有响应的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
let name = 'guan';
setTimeout(() => {
name = 'zhao';
}, 2000);
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
</html>
如下,运行之后,两秒延时之后,DOM文本展示并不会自动改成zhao
,而是一直展示初始化的guan
:
ref()
概念、原理 与 实战使用**
ref
**可以 用于处理基础类型的数据
**,赋能**响应式
**;** 原理:通过 proxy 将数据
封装成 类似proxy({value: '【变量值】'})
**这样的一个**响应式引用
**,** 当**数据
**变化时,就会 触发**template
**等相关UI的更新 【赋予 非data中定义的变量 以**响应式
**的能力 —— 原先,我们是借助Vue的**data函数
**,完成**响应式变量
**的定义的; 有了**ref
**之后,我们可以不借助**data
**中的定义, 而直接在**普通的函数
**中对**js变量
**做**proxy
**封装, 就可以对普通的js引用
赋能**响应式
**了】;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { ref } = Vue;
let name = ref('guan');
setTimeout(() => {
name.value = 'zhao';
}, 2000);
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
</html>
运行效果:
两秒后自动变化:
reactive()
概念、原理 与 实战使用**
reactive()
**用于处理非基础类型的数据(如Object、Array)
**,赋能**响应式
**;** 原理类似**ref()
**,只是处理的数据格式不同而已;
如下,普通的Object类型
是没有响应式
的效果的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj.name}}</div>
`,
setup(props, context) {
// const { ref } = Vue;
const nameObj = { name: 'guan'};
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
return { nameObj }
}
});
const vm = app.mount('#heheApp');
</script>
</html>
运行效果,两秒后无反应:
使用**reactive()
**处理**Object类型
**后,具备**响应式
**的能力:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj.name}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
const nameObj = reactive({ name: 'guan'});
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
return { nameObj }
}
});
const vm = app.mount('#heheApp');
</script>
</html>
运行效果:
两秒后自动变化:
使用**reactive()
**处理**Array类型
**数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj[0]}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
const nameObj = reactive([123, 99, 567]);
setTimeout(() => {
nameObj[0] = 666;
}, 2000);
return { nameObj }
}
});
const vm = app.mount('#heheApp');
</script>
</html>
运行效果:
两秒后自动变化:
readonly
限制对象的访问权限使用**readonly()
**封装对象,可以限制对象为**只读权限
**;
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj[0]}}</div>
<div>{{copyNameObj[0]}}</div>
`,
setup(props, context) {
const { reactive, readonly } = Vue;
const nameObj = reactive([123, 99, 567]);
const copyNameObj = readonly(nameObj);//----
setTimeout(() => {
nameObj[0] = 666;
copyNameObj[0] = 666;//----
}, 2000);
return { nameObj, copyNameObj }//----
}
});
const vm = app.mount('#heheApp');
</script>
运行两秒之后会有相应的报错:
如下的解构是行不通的,
const { name } = nameObj;
**只能拿到**nameObj
**的值,拿不到**proxy
**对象;**
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
const nameObj = reactive({ name: 'guan'});
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
const { name } = nameObj;
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
赋值时 加上**{}
**,会 直取 其 赋值右侧 Object的值;
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
const nameObj = reactive({ name: 'guan'});
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
const { name } = nameObj;
console.log(name);
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
直接赋值,便是直接赋值一份引用;
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
const nameObj = reactive({ name: 'guan'});
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
const name = nameObj;
console.log(name);
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
toRefs()
对reactive
对象进一步封装---
toRefs() expects a reactive object
**;** 首先,**toRefs()
**需要接受一个**reactive
**对象; 即,传给**toRefs()
**之前,需要先用**reactive()
**进行封装; --- 使用**toRefs()
**对**reactive
**封装的对象 进一步封装, 便可以顺利解构; --- 原理:**toRefs()
**将类似**proxy({name:'guan'})
**的结构, 转化成类似**{ name: proxy( {value:'guan'}) }
**的结构, 这里可以看到**name
**键的值,其实就类似于**ref
**的处理结果; 然后使用**const { name }
**对**{ name: proxy( {value:'guan'}) }
**进行解构赋值, 左侧**name
**变量 拿到的就是**proxy( {value:'guan'})
**这一部分的值, 所以放入DOM节点展示时候, 直接使用**name
**即可; --- !!!注意: 类似**reactive()
**的处理结果, 即**proxy({name:'guan'})
**的结构, 放入DOM节点展示时候,需要使用**nameObj.name
**的格式; 而类似**ref()
**的处理结果, 即**proxy( {value:'guan'})
**的结构, 放入DOM节点展示时候,直接使用**nameObj
**的格式即可;
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive, toRefs } = Vue;
const nameObj = reactive({ name: 'guan'});
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
const { name } = toRefs(nameObj);
console.log(name);
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
运行两秒后,自动更新UI:
注意多个属性解构时的写法 以及 return时的写法;
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
<div>{{age}}</div>
<div>{{address}}</div>
`,
setup(props, context) {
const { reactive, toRefs } = Vue;
const nameObj = reactive({ name: 'guan', age: 22, address:'chaozhou'});
setTimeout(() => {
nameObj.name = 'zhao';
nameObj.age = 66;
nameObj.address = 'guangzhou';
}, 2000);
const { name, age, address } = (nameObj);
console.log(name, age, address);
return { name, age, address }
}
});
const vm = app.mount('#heheApp');
</script>
运行结果(当然不会自动更新):
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
<div>{{age}}</div>
<div>{{address}}</div>
`,
setup(props, context) {
const { reactive, toRefs } = Vue;
const nameObj = reactive({ name: 'guan', age: 22, address:'chaozhou'});
setTimeout(() => {
nameObj.name = 'zhao';
nameObj.age = 66;
nameObj.address = 'guangzhou';
}, 2000);
const { name, age, address } = toRefs(nameObj);
console.log(name, age, address);
return { name, age, address }
}
});
const vm = app.mount('#heheApp');
</script>
运行结果:
两秒后自动刷新:
如果意图解构的键,
不存在于**toRefs()
**封装的对象中,
使用时会报错:
<script>
const app = Vue.createApp({
template: `
<div>{{age}}</div>
`,
setup(props, context) {
const { reactive, toRefs } = Vue;
const data = reactive({ name: 'guan'});
const { age } = toRefs(data);
setTimeout(() => {
age.value = 'zhao';
}, 2000);
return { age }
}
});
const vm = app.mount('#heheApp');
</script>
运行结果:
toRef(data, key)
会尝试从data
中读取key
对应的键值,
如果读得到,就直接取值,
如果读不到,会赋值undefined,后续可以为之赋实值:
<script>
const app = Vue.createApp({
template: `
<div>{{age}}</div>
`,
setup(props, context) {
const { reactive, toRef } = Vue;
const data = reactive({ name: 'guan'});
const age = toRef(data, 'age');
setTimeout(() => {
age.value = 'zhao';
}, 2000);
return { age }
}
});
const vm = app.mount('#heheApp');
</script>
运行两秒后自动更新:
setup函数的context参数中的三个属性,即**
attrs, slots, emit
**; 获取方法(解构**context参数
**): setup(props, context) { const { attrs, slots, emit } = context; return { }; }
-- 是一个**
Proxy
**对象; -- 子组件的**none-props属性
**都存进**attrs
**中;
如下,父组件调用子组件,传递**myfield
**属性,
子组件没有用**props
**接收,则**myfield
**作为**none-props属性
**被 子组件承接,
这时候会存进**setup
**函数的**attrs
**中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<child myfield='heheda' />
`
});
app.component('child', {
template:`
<div>child</div>
`,
setup(props, context) {
const { attrs, slots, emit } = context;
console.log(attrs);
return { };
}
})
const vm = app.mount('#heheApp');
</script>
</html>
运行效果:可以看到**attrs
**也是一个**Proxy
**对象
(attrs.myfield)
**打印取值:**
<script>
const app = Vue.createApp({
template: `
<child myfield='heheda' />
`
});
app.component('child', {
template:`
<div>child</div>
`,
setup(props, context) {
const { attrs, slots, emit } = context;
console.log(attrs.myfield);
return { };
}
})
const vm = app.mount('#heheApp');
</script>
-- 是一个**
Proxy
**对象; -- 其中有一个 以为**default
**为键的函数, 这个函数会以虚拟DOM
**的形式,** 返回 传给 子组件的**slot插槽
** 的**组件
**;
打印slots属性:
<script>
const app = Vue.createApp({
template: `
<child>hehehe</child>
`
});
app.component('child', {
template:`
<div>child</div>
<div><slot/></div>
`,
setup(props, context) {
const { attrs, slots, emit } = context;
console.log(slots);
return { };
}
})
const vm = app.mount('#heheApp');
</script>
打印slots属性中的 default函数, 可见其返回的是虚拟DOM:
<script>
const app = Vue.createApp({
template: `
<child>hehehe</child>
`
});
app.component('child', {
template:`
<div>child</div>
<div><slot/></div>
`,
setup(props, context) {
const { attrs, slots, emit } = context;
console.log(slots.default());
return { };
}
})
const vm = app.mount('#heheApp');
</script>
template
的内容,渲染UI<script>
const app = Vue.createApp({
template: `
<child>hehehe</child>
`
});
app.component('child', {
template:`
<div>child Text</div>
<div><slot/></div>
`,
setup(props, context) {
const { h } = Vue;
const { attrs, slots, emit } = context;
return () => h('div', {}, slots.default());
}
})
const vm = app.mount('#heheApp');
</script>
slots
内容通过在其他函数中,使用**this.$slots
**的方式调用到**slots
**内容
<script>
const app = Vue.createApp({
template: `
<child>hehehe</child>
`
});
app.component('child', {
mounted () {
console.log("this.$slots --- ",this.$slots);
},
setup(props, context) {
console.log("context.slots --- ", context.slots);
const { h } = Vue;
const { attrs, slots, emit } = context;
return () => h('div', {}, slots.default());
}
})
const vm = app.mount('#heheApp');
</script>
运行结果如下,可以看到跟**setup
**函数的**context.slots
**是一样的:
<script>
const app = Vue.createApp({
methods: {
handleChange() {
alert('hehehe');
}
},
template: `
<child @change="handleChange">hehehe</child>
`
});
app.component('child', {
template: `
<div >hehehe</div>
`,
mounted () {
this.$emit('change');
}
})
const vm = app.mount('#heheApp');
</script>
<script>
const app = Vue.createApp({
methods: {
handleChange() {
alert('hehehe');
}
},
template: `
<child @change="handleChange">hehehe</child>
`
});
app.component('child', {
template: `
<div @click="handleClick">6666</div>
`,
setup(props, context) {
const { h } = Vue;
const { attrs, slots, emit } = context;
function handleClick() {emit('change');}
return { handleClick };
}
})
const vm = app.mount('#heheApp');
</script>
运行,点击文本:
setup中,
--- const inputValue = ref('6666');
**定义到一个**proxy
**对象,**
可以将此对象 用于setup外的 template中,
完成**双向绑定
**中的一环——**数据字段映射到 UI
**!!!;
--- handleInputValueChange
**定义一个方法;**
运行时,将对应**触发组件
**的 内容
**,**
赋值给 inputValue
**,**
完成**双向绑定
**中的另一环——**UI 映射到数据
**!!!;
template中,
:value="inputValue"
**使得对象的内容 初始化显示**inputValue
**的内容;**
--- @input="handleInputValueChange"
**使得输入框被用户输入新的内容时,**
会调用对应的方法,这里调用:
<script>
const app = Vue.createApp({
setup() {
const { ref } = Vue;
const inputValue = ref('6666');
const handleInputValueChange = (e) => {
console.log("e --- ",e);
console.log("e.target.value", e.target.value);
inputValue.value = e.target.value;
}
return {
inputValue,
handleInputValueChange
}
},
template: `
<div>
<div>
<input :value="inputValue" @input="handleInputValueChange"/>
<div>{{inputValue}}</div>
<button>提交</button>
</div>
<ul>
<li>1</li>
<li>2</li>
</ul>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
运行效果:
--- setup 中定义 数组list,以及 handleSubmit处理函数,
并都在return中返回;
--- template中,
button添加click事件回调;
li 标签,使用v-for,完成列表渲染:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
setup() {
const { ref, reactive } = Vue;
const inputValue = ref('6666');
const list = reactive([]);
const handleInputValueChange = (e) => {
inputValue.value = e.target.value;
}
const handleSubmit = () => {
console.log("now, inputValue.value --- ", inputValue.value);
list.push(inputValue.value);
}
return {
list,
inputValue,
handleInputValueChange,
handleSubmit
}
},
template: `
<div>
<div>
<input :value="inputValue" @input="handleInputValueChange"/>
<button @click="handleSubmit">提交</button>
</div>
<ul>
<li v-for="(item, index) in list" :key="index">{{item}}</li>
</ul>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
</html>
运行效果:
如下,将 setup()中, --- 与 list 相关的定义和操作函数, 封装到
listHandleAction
**中,然后return;** --- 与 inputValue 相关的定义和操作函数, 封装到inputHandleAction
**中,然后return;** --- 最后setup()
调用以上两个**业务模块封装函数
**, 解构**返回的内容
**,进行**中转调用
**; 【分模块 聚合**业务逻辑
**,只留一个**核心方法
**进行**统筹调度
**,有**MVP
**那味了】 【这样设计,业务分明,方便定位问题,可读性、可维护性高!! 】
<script>
const listHandleAction = () => {
const { reactive } = Vue;
const list = reactive([]);
const addItemToList = (item) => {
console.log("now, item --- ", item);
list.push(item);
}
return { list, addItemToList }
}
const inputHandleAction = () => {
const { ref } = Vue;
const inputValue = ref('');
const handleInputValueChange = (e) => {
inputValue.value = e.target.value;
}
return { inputValue, handleInputValueChange }
}
const app = Vue.createApp({
setup() {
const { list, addItemToList } = listHandleAction();
const { inputValue, handleInputValueChange } = inputHandleAction();
return {
list, addItemToList,
inputValue, handleInputValueChange
}
},
template: `
<div>
<div>
<input :value="inputValue" @input="handleInputValueChange"/>
<button @click="addItemToList(inputValue)">提交</button>
</div>
<ul>
<li v-for="(item, index) in list" :key="index">{{item}}</li>
</ul>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
运行效果:
<script>
const app = Vue.createApp({
setup() {
const { ref, computed } = Vue;
const count = ref(0);
const handleClick = () => {
count.value += 1;
}
const countAddFive = computed(() => {
return count.value + 5;
})
return { count, countAddFive, handleClick }
},
template: `
<div>
<div>
<span @click="handleClick">{{count}}</span> --- {{countAddFive}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
运行结果:
这个对象包含两个函数属性,
第一个是**get
**函数,其内容即取值时候返回的内容,同默认用法;
第二个是**set
**函数,当试图修改**computed
**变量的值时,就会回调这个方法,
接收的参数,即**试图修改的值
**:
如下,试图在3秒后修改computed
变量countAddFive
的值,
这时回调set方法:
<script>
const app = Vue.createApp({
setup() {
const { ref, computed } = Vue;
const count = ref(0);
const handleClick = () => {
count.value += 1;
}
const countAddFive = computed({
get: () => {
return count.value + 5;
},
set: (param) => {
count.value = param -5;
}
})
setTimeout(() => {
countAddFive.value = 1000;
}, 2000);
return { count, countAddFive, handleClick }
},
template: `
<div>
<div>
<span @click="handleClick">{{count}}</span> --- {{countAddFive}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
运行3s后:
<script>
const app = Vue.createApp({
setup() {
const { reactive, computed } = Vue;
const countObj = reactive({ count: 0 });
const handleClick = () => {
countObj.count += 1;
}
const countAddFive = computed({
get: () => {
return countObj.count + 5;
},
set: (param) => {
countObj.count = param -5;
}
})
setTimeout(() => {
countAddFive.value = 1000;
}, 2000);
return { countObj, countAddFive, handleClick }
},
template: `
<div>
<div>
<span @click="handleClick">{{countObj.count}}</span> --- {{countAddFive}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
运行效果同上例;
如下,
---**watch
**一个参数为要监听的引用,
第二个参数为函数类型,当监听的引用发生变化时会回调,
其有两个参数,一个是当前(变化后的)值,一个是变化前的值;
--- input
**组件中,**v-model
**完成**双向绑定
**!!!**
--- input
**输入内容时,触发** 双向绑定
**的特性,**
内容映射到**name
**引用上,
由**ref
**的**响应特性
**,**name
**的内容又映射到**{{name}}
**这**DOM节点
**上:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
setup() {
const { ref, watch } = Vue;
const name = ref('heheda')
watch(name, (currentValue, prevValue) => {
console.log(currentValue, prevValue);
})
return { name }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
</html>
运行效果:
所以,这里主要是
---将watch的 第一个参数改成 函数;
---使用toRefs,简化传递过程;
(不然template要接收和处理的就是 nameObject.name了,而不是这里的name)
<script>
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue;
const nameObj = reactive({name: 'heheda'});
watch(() => nameObj.name, (currentValue, prevValue) => {
console.log(currentValue, prevValue);
});
const { name } = toRefs(nameObj);
return { name }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
运行效果同上例;
注意watch的参数写法,
一参写成,以**函数类型
**为**元素
**的**数组
**;
二参,参数列表写成两个数组,
第一个为current值数组,第二个为prev值数组;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue;
const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
watch([() => nameObj.name, () => nameObj.englishName],
([curName, curEngN], [prevName, prevEngN]) => {
console.log(curName, prevName, curEngN, prevEngN);
});
const { name, englishName } = toRefs(nameObj);
return { name, englishName }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
<div>
EnglishName:<input v-model="englishName">
</div>
<div>
EnglishName is {{englishName}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
</html>
运行,先在Name输入框输入,后再EnglishName框输入,效果:
函数中,使得纯函数 变成 非纯函数的 异步处理等部分逻辑块, 称之为**
effect块
**;
watch
**是**惰性
**的,只有**watch
**监听的字段发生变化时,**
watch
**的处理逻辑才会被回调;**
--- watchEffect
**是**即时性
**的,也就是除了**watch
**的**回调特性
**,**
watchEffect
**的**处理逻辑
**还会在**页面渲染完成时
**立马先执行一次,**
即**watchEffect
**监听的字段未曾改变,
watchEffect
**就已经执行了一次;**
(实例可以见下例运行效果)watch
**需要写明**监听字段
**,**
watchEffect
**不需要,直接写**处理逻辑
**即可,**
底层封装
**会**!自动监听!
**所写**处理逻辑
**中用到的**!所有字段!
**;**如下例子中,
watchEffect
的处理逻辑——console.log(nameObj.name, nameObj.englishName);
,
仅一行代码,
完成对nameObj.name
和nameObj.englishName
两个字段的监听,
并完成了处理逻辑;
watch
**可以直接从**参数列表
**中获取到**之前(变化前)的值
**和**当前(变化后)的值
**,**
watchEffect
**不行,处理逻辑中拿到的直接就是**当前(变化后)的值
**;**watch
配置 immediate属性,可使具备同**watchEffect
**的 即时性<script>
const app = Vue.createApp({
setup() {
const { reactive, watch, watchEffect, toRefs } = Vue;
const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
watchEffect(() => {
console.log(nameObj.name, nameObj.englishName);
})
const { name, englishName } = toRefs(nameObj);
return { name, englishName }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
<div>
EnglishName:<input v-model="englishName">
</div>
<div>
EnglishName is {{englishName}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
跟紧**console.log(nameObj.name, nameObj.englishName);
**,
先在Name输入框输入123,后再EnglishName框输入456,运行效果:
注意第一行打印,第一行是页面渲染完成时立马执行, 用户未曾输入内容,**
watchEffect
**监听的字段未曾改变,watchEffect
**就已经执行了一次,体现**watchEffect
**的**即时性
**!!!**
将**
watch / watchEffect
**的**函数返回值
** 赋给一个字段(如下**stopWatch / stopWatchEffect
**); 接着在**watch / watchEffect
**的**处理逻辑
**中, 编写类似**setTimeout
**的异步函数, 并在其中调用 与 刚刚定义的**字段
** 同名的 函数(如下**stopWatch() / stopWatchEffect()
**), 即可停止**对应的监听
**;
<script>
const app = Vue.createApp({
setup() {
const { reactive, watch, watchEffect, toRefs } = Vue;
const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
const stopWatch = watch([() => nameObj.name, () => nameObj.englishName],
([curName, curEngN], [prevName, prevEngN]) => {
console.log(curName, prevName, curEngN, prevEngN);
setTimeout(() => {
stopWatch();
}, 3000);
});
const stopWatchEffect = watchEffect(() => {
console.log(nameObj.name, nameObj.englishName);
setTimeout(() => {
stopWatchEffect();
}, 3000);
})
const { name, englishName } = toRefs(nameObj);
return { name, englishName }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
<div>
EnglishName:<input v-model="englishName">
</div>
<div>
EnglishName is {{englishName}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
运行,可以看到,
前3s(我们设定的时延)两个输入框是可以响应监听的,
但是3s之后,无论怎么输入内容,console也不会打印log,
因为这时两个监听效果已经取消了:
watch
配置 immediate属性,可使具备同watchEffect
的 即时性如下,使用**{ immediate: true}
**为 watch
配置 immediate属性,
可使具备同**watchEffect
**的 即时性:
<script>
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue;
const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
const stopWatch = watch([() => nameObj.name, () => nameObj.englishName],
([curName, curEngN], [prevName, prevEngN]) => {
console.log(curName, prevName, curEngN, prevEngN);
}, { immediate: true});
const { name, englishName } = toRefs(nameObj);
return { name, englishName }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
<div>
EnglishName:<input v-model="englishName">
</div>
<div>
EnglishName is {{englishName}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
运行效果如下,**undefined
**是因第一次执行时,
监听的变量还没有变化,
所以就没有**先前值(prevValue)
**的说法,只有**当前值(currentValue)
**:
--- Vue3.0提供了一些对应生命周期的,可以写在**setup
**函数中的回调方法;
(具体请看 下方例程)
--- setup
**函数的**执行时间点
在于**beforeCreate
**和**Created
**之间,
所以**CompositionAPI
**里边是没有类似**onBeforeCreate
**和**onCreated
**的方法的,
要写在这两个周期中的逻辑,
直接写在**setup
**中即可;
下面是两个Vue3.0引入的新钩子:
--- onRenderTracked
渲染跟踪,
跟踪 收集响应式依赖的时机,
每次准备开始渲染时(onBeforeMount后,onMounted前)回调;
--- onRenderTriggered
渲染触发,
每次**触发
**页面重新渲染时回调,
回调后,下一轮的 onBeforeMount紧跟其后;
<script>
const app = Vue.createApp({
setup() {
const { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
onRenderTracked, onRenderTriggered } = Vue;
const heheda = ref('heheda');
onBeforeMount(() => {
console.log('onBeforeMount');
}),
onMounted(() => {
console.log('onMounted');
}),
onBeforeUpdate(() => {
console.log('onBeforeUpdate');
}),
onUpdated(() => {
console.log('onUpdated');
}),
onRenderTracked((event) => {
console.log('onRenderTracked', event);
}),
onRenderTriggered((event) => {
console.log('onRenderTriggered', event);
})
const handleClick = () => {
heheda.value = 'lululu';
console.log("you had clicked the text!");
}
return { heheda, handleClick }
},
template: `
<div @click="handleClick">
{{heheda}}
</div>
`
});
const vm = app.mount('#heheApp');
</script>
运行效果:
--- 父组件拿出**provide
**,提供键值对;
--- 子组件拿出**inject
**,一参为接收键,二参为默认值;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
setup() {
const { provide } = Vue;
provide('name', 'li');
return { }
},
template: `
<div>
<child />
</div>
`
});
app.component('child', {
setup() {
const { inject } = Vue;
const name = inject('name', 'default');
return {name}
},
template: `
<div>{{name}}</div>
`
})
const vm = app.mount('#heheApp');
</script>
</html>
运行结果:
ref
实现 响应特性 以及 readonly
实现 单向数据流--- setup中, 借**provide
**传输的 数据字段 配合上**ref
**,
使之具备**响应
**的特性;
--- 父组件提供 更改数据的接口,
在使用**provide
**传递 数据字段时,加上 readonly
**包裹,**
使得子组件 需要更改 父组件传递过来的数据字段 时,
无法直接 修改字段,
需调用 父组件的接口方法 更改,
按 单向数据流 规范编程;
<script>
const app = Vue.createApp({
setup() {
const { provide, ref, readonly } = Vue;
const name = ref('li');
provide('name', readonly(name));
provide('changeName', (value) => {
name.value = value;
})
return { }
},
template: `
<div>
<child />
</div>
`
});
app.component('child', {
setup() {
const { inject } = Vue;
const name = inject('name', 'default');
const changeName = inject('changeName');
const handleClick = () => {
changeName('lin');
}
return { name, handleClick }
},
template: `
<div @click="handleClick">{{name}}</div>
`
})
const vm = app.mount('#heheApp');
</script>
运行效果:
点击:
前面笔记《Vue3 | Mixin、自定义指令、Teleport传送门、Render函数、插件 详解 及 案例分析》有写到普通场景的ref指定;
--- setup中的ref是前面说的生成响应式字段的意思;
--- template中的ref是获取对应的dom节点;
--- 而当 template中的ref
**指定的字段名,**
跟setup中的ref生成的响应式字段名一样的时候,两者就会关联起来;
如下,
template中和setup中的字段**heheda
**关联起来,
在setup的onMounted中,使用这个DOM节点字段,
打印DOM代码:
<script>
const app = Vue.createApp({
setup() {
const { ref, onMounted } = Vue;
const heheda = ref(null);
onMounted(() => {
console.log(heheda);
console.log(heheda.value);
})
return { heheda }
},
template: `
<div>
<div ref="heheda">lueluelue</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
运行结果: