watch 是一个侦听某一个ref对象 或 reactive 的对象 的变化 测试 watch 一定要注意 不能够在回调期间 如果发生变化,所执行的函数中 再次使用这个侦听对象
1、watch、watchEffect 侦听函数,它侦听的仅仅是数值的变化,如果数值是不变化的,那么它就没有必要进行刷新执行
2、为什么test_watch++这个侦听函数是执行的,但是为什么for 循环中为什么就不执行了? 侦听函数的特性,在不给它任何选项的情况下,watch、watchEffect 它都是惰性的
watch、watchEffect 侦听函数,它侦听的仅仅是数值的变化,如果这个数值是不变化的,那么它就没有必要进行刷新执行。为什么 test_watch++ 这个侦听函数是执行,但为什么 for 循环中为什么就不执行了呢?浏览器什么时候更新刷新这是一个定时的。它是根据 计算机系统设置的刷新频率 同步刷新的。在同一刷新时间内,多次调用 侦听 函数,那么它将以最后一项为准。但有的时候这个惰性原理,其实它不是我们想要的结果,我们想一次性发送批量请求,那么就是改变 侦听 函数的 选项
watch 我们知道它是没有初始化特征的,但是 watchEffect 它是具有初始化这个特点,但是很多时候这并不是我们想要的结果,我们希望 watchEffect 和 watch 函数一样,仅在 相关联对象发生改变时才触发它,那么可以使用 flush: 'post'
watchEffect 默认它 flush 选项 值 'ref' 它是具有初始化的。 更改 post 后,初始化将不再执行。 watch 它自身就没有初始化这个特性,即便你将 flush 选项修改为 ref 也不会有初始化特性。
侦听多个数据 watch 当中去侦听多个 ref 对象或者属性, 但是属性 需要使用 getter 函数来进行返回,多个属性之间,在调用回调函数时的参数,是可以自定义格式的, 你可以定义为一个对象,也可以定义一个列表。
<p>如果说想把这个全称 显示在页面上 computed 主动调用 并且 它会产生一个实际可以使用 响应式 对象。 </p>
<p>fullName = Vue.computed(()=> ref_first_name + ref_last_name ) template 当中 直接输出 fullName</p>
<p>如果说我并不想把这个 fullName 直接显示在页面上,而是 通过这个合成的 名称 调用某一个查询函数,然后将 这个人的相关资料显示在 页面上</p>
<p>如果说相关资料是复杂的,那我可能会使用 子组件来显示 相关资料 把 异步请求函数的返回值,使用props 传递子组件,并且显示出来.</p>
<p>页面就不需直接使用 fullName 来显示。仅需要一个合成的名称 ,甚至连变量都不用了. watch、watchEffect</p>
<p>Vue.watch([ref_first_name, ref_last_name], newValue => { request_person_data(newValue) })</p>
<p>promise.then(data => { ref_data = data } )</p>
<p>template :data="ref_data" 因为它是响应式的 ref 对象 所以 这个信息将自动更新至 子组件上。</p>
<p>还会对当前名称 进行归类 会以一个常量的形式来标注一些其它 信息,但是这些信息并不在 侦听范围之内。</p>
<p>这种情况下就必须使用 watch,因为如果你使用 watchEffect 将导致 这个没有用信息,在侦听范围之内。</p>
<p>如果说这些信息,并不是在页面上去显示的情况下,可以使用普通变量,而不是响应式的 ref 对象。</p>
<h3>侦听响应式对象</h3>
<p>
<button @click="test_array.push('测试元素')">添加一个元素</button>
<button @click="test_array.pop()">删除一个元素</button>
</p>
{{ test_object }};
<p><button @click="test_object.friend.push('傻大木'); test_object.family.father = '李雷的爹'">点击修改</button></p>
<p>watch 监听的属性 在使用 getter 函数,返回要监听 的属性时,直接 返回一个 reactive 的话,深层的属性的监听 默认是无效的。</p>
<p>但是如果说你直接 使用一个 reactive 对象的话,对深层的监听,就是有效的了.</p>
写一个模拟搜索的函数
js部分
async function get_product(name)
{
const time = Math.floor(Math.random() * 4) + 2;
const message = `商品${name}的搜索共计有${Math.floor(Math.random() * 10000)} 件.`;
const reply_promise = new Promise(resolve => setTimeout(resolve, time * 1000 , { time: time + 's', message }));
// 从之到上面函数的开始,同步执行
await reply_promise; // 开始 await 等待 这个 promise 处于 落定 resolve 状态
// 下面是在 promise 落定之后才执行的 操作.
// 我们就可以对这个返回值进行操作了, 操作 返回时间
reply_promise.then(response => response['replyTime'] = new Date());
return reply_promise; // return 是当时就返回的,返回值就是这个 replay_promise 但是它现在还是一个 pending 状态;
// 在这个函数的返回值 接收之后,再 then 也就是这个 promise 在resolve 状态后,再执行什么东西!
}
//==========================================
function select_start()
{
// 开始搜索
// 禁用按钮
button_disabled.value = true;
select_status.value = `正在搜索[${select_product.value}]请稍后....`;
// 执行搜索
const reply_promise = get_product(select_product.value); // 商品名称搞进去
reply_promise.then(data => {
// 更新 搜索状态
select_status.value = `${data.message} 共计搜索用时: ${data.time}!`;
if(product_name.value === '')
{
// 说明用户没有输入,用的是推荐列表中的
// 这个 product_name_index 必须使用 ref 如果不是ref 对象那么它就没有办法及时给 computed 计算属性发送更新信号.
product_name_index.value = product_name_index.value + 1 >= product_list.length?0:product_name_index.value + 1;
}else
{
product_name.value = '';
}
// 添加搜索记录
// 异步函数因为不知道什么时候返回,所以返回后的处理都在异步函数中执行。
// 更新全部数组
// product_record.value = product_record.value.concat([`${data.message} 用时: ${data.time}`]);
// product_record.value.reverse();
// 局部更新数组
product_record.push(`${data.message} 用时: ${data.time}`);
// 解禁按钮
button_disabled.value = false;
});
// 成功以后怎么样,失败之后又怎么样~~
// 如果这个 product_name 如果为 '' 情况下 那么我们就让 product_name_index++ 下一个搜索, 如果说 product_name 存在 说明用户自己定义的搜索项,清空这个搜索就ok了`
}
//==========================
function animation_enter(el, done)
{
gsap.set(el, {
x: 100,
opacity: 0
});
gsap.to(el, {
x: 0,
opacity: 1,
duration: 0.5,
onComplete: done
})
}
function test_func()
{
console.log('Run >>>> 被节流函数被运行!');
}
// 被节流的函数
const test_func_throttle = lodash.throttle(test_func, 5000, { leading: false, trailing: true });
function test_throttle()
{
console.log('测试防抖、节流函数!');
// test_func();
// 这个节流函数,是以上一次调用该函数的时间为准 开始计算间隔时间
// 和我们和次自己写的那个不一样,每一次调用 都重置 间隔时间
// 调用函数是否允许出现在节流时间之前
// leading 设置为 true 的情况下 将允许在节流时间之前 即可调用. 即时点击,即时就调用一次,然后开始重新计时
test_func_throttle();
}
function input_event(event)
{
console.log(event.target.value);
// 2秒 之后直接 调用 搜索 功能
// 直接拿这个字符串来调用搜索
// 这个功能是避免 在短时间内进行重复性的搜索
// 如果说在这个位置调用 搜索 功能 而这个搜索功能 它是一个异步函数 大概4-6秒钟的时间,才会返回 返回值
// 2秒的防抖其实对于搜索框的更新没有什么太大的价值。
// 它的价值在 短时间内不会重复的调用 搜索 函数
}
const input_event_throttle = lodash.throttle(input_event, 2000, { leading: false });
const input_value = Vue.ref('');
function test_select_func(select_str)
{
return new Promise(resolve => setTimeout(resolve, (Math.floor(Math.random() * 5) + 2) * 1000, `搜索函数被调用,搜索值:${select_str}!`));
}
// 怎么去触发这个 搜索 函数呢?
// test_select_func('家电').then(console.log);
// Vue.watchEffect() // 初始
/*
Vue.watch(input_value, (newValue, _, onInvalidate) => {
test_select_func(newValue).then(console.log);
// 什么监听周期 当一个值被改变后,运行当前函数,当前函数如果未执行完成,则进入了下一个监听周期,那么 就像这个动画一样,它就被强制取消掉了.
// 我们怎么才能 监听强制取消 事件?
// 使用 回调函数中的 第三个参数 onInvalidate 来注册 强制取消侦听事件的回调
onInvalidate(() => {console.log('侦听事件被强制中断!')});
// 虽然说它可以检测到 侦听事件的被强制中断 但是它并不能 改变 异步函数的运行。
}
);
*/
Vue.watch(input_value, async (newValue, _, onInvalidate) => {
// 同步执行
let print = true;
onInvalidate(() => print = false); // 如果代码被强制中断,则我就取消这一次输出.
const select_str = await test_select_func(newValue); // 返回一个 promise resolve 调用值
// 下面的代码,将在 promise 处于 resolve 以后才会被执行.
if(print)
{
console.log(select_str);
}else {
console.log('已被丢弃的搜索:', select_str);
}
})
// 这个整个的搜索框 它是个线性的
// =============================================================
// 侦听函数的惰性特点
const test_watch = Vue.ref(0);
Vue.watch(test_watch, newValue => console.log(newValue), {flush: 'sync'});
// flush 共有三个 选项: ref(默认) post 延时 sync 同步
// 设置为同步的情况下 sync 将关闭 侦听函数默认的 惰性原理
// 这个 flush 选项 watch、 watchEffect 都是一样的.
// post 指的是延时 延到什么? 延时到 mounted 生命周期之后.
// Vue.watchEffect(onInvalidate => {}, { flush: 'sync'});
function test_watch_func()
{
// test_watch ref对象 它的值被改变了 10 次
for(let index = 1; index <= 10; index++)
{
test_watch.value = index;
}
/*
test_watch.value = 1;
test_watch.value = 2;
test_watch.value = 3;
test_watch.value = 4;
test_watch.value = 5;
test_watch.value = 6;
*/
}
const test_watch_two = Vue.ref(0);
// Vue.watch(test_watch_two, newValue => console.log('检测到 test_watch_two ref对象被改变,值为:', newValue), { flush: 'post' });
Vue.watchEffect(() => {console.log('检测到 test_watch_two ref对象被改变,值为:', test_watch_two.value)}, { flush: 'post' })
// 说明这个Vue.watch 侦听事件被创建后就马上 起效了
// Vue.watchEffect flush 为 post 的情况下,它将不再对 ref 或 reactive 进行初始化处理。像 watch 函数一样,仅在某一侦听对象被改变时才会触发。
test_watch_two.value = 999;
const test_watch_reactive_object = Vue.reactive({ num1: 10, num2: 100, num3: 1000 });
// reactive 它是递归所有深度的.
// Vue.watch(() => {return {num1: test_watch_reactive_object.num1, num2: test_watch_reactive_object.num2 } }, (newValue) => {console.log(newValue) });
Vue.watch(() => {return [test_watch_reactive_object.num1, test_watch_reactive_object.num2 ] }, (newValue) => {console.log(newValue) }, { flush: 'sync' });
// 侦听器可以侦听 单个对象 还可以侦听 单个对象属性
// 多个 ref 对象 多个对象属性,必须是 reactive 对象属性
// 列表进行监听
const test_array = Vue.reactive([]); // {} 其实它就是一个对象
// Vue.watch(test_array, (newValue, oldValue) => { console.log('new value:', newValue, 'old value:', oldValue) });
// 回调函数中的 newValue oldValue 一毛一样.
Vue.watch( () => Array.from(test_array), (newValue, oldValue) => {
console.log('new value:', newValue);
console.log('old value:', oldValue);
});
// test_array 被解构以后,就好了.
// watch 当中的这个 [...test_array] 其实就是一个字页面字义方式,换句话说,它就是创建一个新的 array, 和当前array是一致的.
// 为什么直接监听 test_array newValue oldValue 为什么值是一样的.
// Vue.reactive 先是创建了一个,更新即可以发送 被更新信号 的对象。
// 这个列表在更新时,将会发送一个被更新的信号.
// 侦听一个数组是没有太大的意义的
const test_object = Vue.reactive({ name: '李雷', friend: ['小红', '吉姆'], family: { father: '李雷的爸', mather: '李雷的妈'}});
/*
Vue.watch(() => ({friend: [...test_object.friend], father: test_object.family.father}),(newValue, oldValue) => {
console.log('new value:', newValue);
console.log('old value:', oldValue);
})
*/
// 大部分场景下是用不到 整个对象的监听的
/*
Vue.watch(() => test_object, (newValue, oldValue) => {
console.log('new value:', newValue.friend, newValue.family.father);
console.log('old value:', oldValue.friend, oldValue.family.father);
}, { deep: true });
*/
// Vue.reactive 将会递归所有深度上的 对象吧? 在修改这个对象上的任何属性,包括 深层属性,都将会发送一个 自身属性被修改的 信号。 watch 将捕获这个信号 进行处理。
// newValue 和 oldValue 的输出,就证明 reactive 对象已经把信号发送出来了,而且 watch 已经执行了.
// 虽然说 watch 可以直接对 reactive 深层对象进行侦听,但是会遇到一个问题。
// oldValue 和 newValue 一致的问题。
// 解决这个问题的方案 就是将 某一个对象 再重新 复制一份. 数组 可以通过 Array.from 解构等这种方法 将数组进行一次 复制。
// 深层嵌套的对象怎么解决类?
Vue.watch(() => {
// 这个位置返回的,不应该是原对象,而是原来一个对象的 幅本。
// 更关键的问题是,你不能直接使用 Object.assign 这种东西,因为这么复制出来的是 浅复制。
// 如何进行深 复制呢?
// 通过原生 js JSON
// return JSON.parse(JSON.stringify(test_object)); // 对这个对象进行 先JSON序列化,然后再将JSON转换为对象
// 序列化 JSON 有一个弊端 JSON 并不是一个专业 对 javascript 对象 进行字符串序列的工具。它是一种数据类型,用于 各种语言中的 数据类型,进行字符串处理,便于传输。
// 对象中如果存在 方法、函数 那就不行了。 因为JSON 不能转换函数
// 以后记着一个问题,监听的数据,不要放函数
// 如果说有函数,那就 () => { 从这里边 返回单个 对象的属性 进行监听 }
return lodash.cloneDeep(test_object);
}, (newValue, oldValue) => {
console.log('new value:', newValue.friend, newValue.family.father);
console.log('old value:', oldValue.friend, oldValue.family.father);
}, { deep: true });
// newValue 传递的是 整个对象
test_watch_reactive_object.num1 = 777;
test_watch_reactive_object.num2 = 888;
return {
select_product,
select_list,
product_list,
select_start,
product_record,
select_status,
animation_enter,
disabled: button_disabled,
test_throttle,
test_func_throttle,
input_event: input_event_throttle,
input_value,
test_watch_func,
test_watch,
test_array,
test_object
}
},
mounted()
{
console.log(this);
}
html部分
<p>
<label>搜索商品: <input type="text" size="5" v-model.lazy="select_product"></label>
<button @click="select_start" :disabled="disabled">搜索商品</button></p>
<p class="select_status">{{ select_status }}</p>
<p>搜索记录: </p>
<p v-if="!product_record.length">暂无记录</p>
<ul v-else class="record-box">
<!-- <li v-for="(record, index) in product_record" :key="index" >{{ record }}</li>-->
<transition-group @enter="animation_enter" :css="false" :appear="true">
<li
v-for="index in product_record.length"
:key="product_record.length - index"
>{{ index }}、{{ product_record[product_record.length - index] }}</li>
<!-- index 将 从1开始至 product_record.length -->
</transition-group>
<!-- 使用这个 index 就要造成 VNode 更新(不会触发动画) 生成添加VNode的时候才会触发动画. -->
</ul>
测试防抖
<p>测试一下 lodash 模块中的 throttle 防抖节流函数:</p>
<button @click="test_throttle">点击测试</button>
<button @click="test_func_throttle.cancel">取消调用</button>
<button @click="test_func_throttle.flush">立即调用</button>
针对不停的点击搜索框按钮问题 有两个解决办法 1、 禁用按钮 搜索过程中不让点 2、利用防抖函数
<p>就写这个例子:</p>
<p>搜索推荐1: <input type="text" @input="input_event.cancel();input_event($event);"></p>
<p>调用两个函数1 清除到之前的 防抖调用,2 创建新的防抖调用</p>
<p>在输入框中输入文字,首先要提取到输入的文字.</p>
<p>这个例子的特点是,在Vue中没有任何的变量来存储当前 搜索的字符串.</p>
<p>搜索推荐2: <input type="text" @input="input_value=$event.target.value"></p>
<p>{{ input_value }}</p>
<p>整个搜索结果的 显示框 线性的可以分为以下几个步骤:</p>
<ol>
<li>获取当前用户输入</li>
<li>用户输入事件频繁调用,所以需要添加 防抖函数 来解决 频繁调用 select 搜索函数.</li>
<li>已经防抖过滤掉的 用户输入 调用异步搜索函数,返回结果需要时间.</li>
<li>在异步返回值 未返回之前,如果重复调用 搜索函数,之前的搜索应该是被丢弃掉的。</li>
<li>我们现在把调用 搜索 异步函数操作放在 watch 侦听事件当中了。</li>
<li>在这一次侦听事件 回调函数 完成调用之前,如果再次调用了 侦听函数(在改变侦听变量被改变之后,调用 回调函数,但是在回调函数未完成之前,又一次侦听到 改变了。)</li>
<li>那么上一次的侦听事件 将强制被取消掉。 使用 watch 的第三个参数 onInvalidate 注册 侦听事件被强制中断后的 回调函数。</li>
</ol>
<p>watch 它的 onInvalidate 它是 回调函数中的第三个参数</p>
<p>watchEffect 它的 onInvalidate</p>
<p>Vue.watchEffect(onInvalidate => {}) 自动检测当前回调函数当中,相关 ref对象 reactive 对象 是否发生改变。</p>
<p>侦听函数的 起效时间</p>
<p>侦听函数的特性,在不给它任何选项的情况下,watch、watchEffect 它都是惰性的</p>
<p>
<button @click="test_watch_func">点击执行 ref 对象的 for循环</button>
<button @click="test_watch++">点击 test_watch++</button>
</p>
<p>它仅输出一个 10</p>
<p>这是为什么呢?这就是它的惰性原理!</p>
<ol>
<li>watch、watchEffect 侦听函数,它侦听的仅仅是数值的变化,如果这个数值是不变化的,那么它就没有必要进行刷新执行。</li>
<li>为什么 test_watch++ 这个侦听函数是执行,但为什么 for 循环中为什么就不执行了呢?</li>
<li>浏览器什么时候更新刷新这是一个定时的。它是根据 计算机系统设置的刷新频率 同步刷新的。</li>
<li>在同一刷新时间内,多次调用 侦听 函数,那么它将以最后一项为准。</li>
</ol>
<p>但有的时候这个惰性原理,其实它不是我们想要的结果,我们想一次性发送批量请求。</p>
<p>那么就是改变 侦听 函数的 选项</p>
<p>watch 我们知道它是没有初始化特征的,但是 watchEffect 它是具有初始化这个特点,但是很多时候这并不是我们想要的结果,我们希望 watchEffect 和 watch 函数一样,仅在 相关联对象发生改变时才触发它,那么可以使用 flush: 'post'</p>
<p>watchEffect 默认它 flush 选项 值 'ref' 它是具有初始化的。 更改 post 后,初始化将不再执行。 watch 它自身就没有初始化这个特性,即便你将 flush 选项修改为 ref 也不会有初始化特性。</p>
<p>侦听多个数据 watch 当中去侦听多个 ref 对象或者属性, 但是属性 需要使用 getter 函数来进行返回</p>
<p>多个属性之间,在调用回调函数时的参数,是可以自定义格式的, 你可以定义为一个对象,也可以定义一个列表。</p>
<p>firstName lastName 名 姓 名+姓 = 全称</p>
<p>上午 input 是一样的道理</p>
<p>如果说想把这个全称 显示在页面上 computed 主动调用 并且 它会产生一个实际可以使用 响应式 对象。 </p>
<p>fullName = Vue.computed(()=> ref_first_name + ref_last_name ) template 当中 直接输出 fullName</p>
<p>如果说我并不想把这个 fullName 直接显示在页面上,而是 通过这个合成的 名称 调用某一个查询函数,然后将 这个人的相关资料显示在 页面上</p>
<p>如果说相关资料是复杂的,那我可能会使用 子组件来显示 相关资料 把 异步请求函数的返回值,使用props 传递子组件,并且显示出来.</p>
<p>页面就不需直接使用 fullName 来显示。仅需要一个合成的名称 ,甚至连变量都不用了. watch、watchEffect</p>
<p>Vue.watch([ref_first_name, ref_last_name], newValue => { request_person_data(newValue) })</p>
<p>promise.then(data => { ref_data = data } )</p>
<p>template :data="ref_data" 因为它是响应式的 ref 对象 所以 这个信息将自动更新至 子组件上。</p>
<p>还会对当前名称 进行归类 会以一个常量的形式来标注一些其它 信息,但是这些信息并不在 侦听范围之内。</p>
<p>这种情况下就必须使用 watch,因为如果你使用 watchEffect 将导致 这个没有用信息,在侦听范围之内。</p>
<p>如果说这些信息,并不是在页面上去显示的情况下,可以使用普通变量,而不是响应式的 ref 对象。</p>
<h3>侦听响应式对象</h3>
<p>
<button @click="test_array.push('测试元素')">添加一个元素</button>
<button @click="test_array.pop()">删除一个元素</button>
</p>
{{ test_object }};
<p><button @click="test_object.friend.push('傻大木'); test_object.family.father = '李雷的爹'">点击修改</button></p>
<p>watch 监听的属性 在使用 getter 函数,返回要监听 的属性时,直接 返回一个 reactive 的话,深层的属性的监听 默认是无效的。</p>
<p>但是如果说你直接 使用一个 reactive 对象的话,对深层的监听,就是有效的了.</p>
ref用于setup函数 中对于基础数据对象的传递 为了使数据具有哦一定的响应性,所以需要将数据进行对象包装,所以要在setup函数中使用ref对象需要使用.value操作