该文章重点来梳理一些重要但隐晦不经人注意的知识点!vue
watchEffect 的特征在 watch 保持一致,因此这里仅仅从 watchEffect 出发点梳理便可node
watchEffect 组件初始化的时候会执行一次,组件卸载的时候会执行一次react
watchEffect 能够返回一个句柄 stop
,再次调用将能够进行注销 watchEffectios
const stop = watchEffect(() => {
/* ... */
})
// later
stop()
复制代码
有时反作用函数会执行一些异步的反作用,这些响应须要在其失效时清除 (即完成以前状态已改变了) 。 watchEffect 函数能够接收一个 onInvalidate 函数做入参,用来注册清理失效时的回调。当如下状况发生时,这个失效回调会被触发:git
watchEffect(onInvalidate => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id has changed or watcher is stopped.
// invalidate previously pending async operation
token.cancel()
})
})
复制代码
重点:es6
Vue 为何采用经过传入一个函数去注册反作用清除回调,而不是从回调返回它(react useEffect)?github
Vue 的回答是由于返回值对于异步错误处理很重要。express
咱们分别来看看 Vue 和 React 的区别:axios
Vuec#
setup() {
const count = ref(0);
function getData() {
return new Promise((resolve, reject) => {
resolve(100);
})
}
const data = ref(null)
watchEffect(async onInvalidate => {
onInvalidate(() => {
console.log('onInvalidate is triggered');
}) // 咱们在Promise解析以前注册清除函数
const data = await getData();
})
return {count};
}
复制代码
React
function App() {
const [count, setCount] = useState(0);
function getData() {
return new Promise((resolve, reject) => {
resolve(100);
})
}
useEffect(()=> {
const getDataAsync = async () => {
const data = await getData();
}
getDataAsync();
return () => {
console.log('onInvalidate is triggered');
}
}, [count]);
return <div></div>
}
复制代码
经过上面 Vue 和 React 能够知道在清除反作用的写法上的差别,Vue 经过 onInvalidate
来处理,而 React 是经过 return
一个函数来处理。
对于 Vue 来讲,Vue 认为处理异步的错误也是很重要的,为何这么说呢,按照 Vue 的写法,watchEffect
传入了一个 async
异步函数,了解过 ES6 的 async/await
内部实现的原理能够知道,async/await
实际上会隐式的返回一个 Promise
,咱们看看文档片断:
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
// spawn 的实现
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
复制代码
意味着 watchEffect
能够链式处理一些内部 Promise
的机制,好比:await
的返回的 Promise
若是触发了 reject
,Vue 依赖这个返回的 Promise
来自动处理 Promise
链上的潜在错误,这就是为何 Vue 说返回值对于异步错误处理很重要。
还有一点就是清理函数 onInvalidate
必需要在 Promise
被 resolve
以前被注册。
相比较于 React 的写法,由于 React 清理反作用的方法是采用 return
一个回调出来,按照这种机制,若是咱们在 useEffect
函数中传入 async/await
函数,咱们根据对 async/await
的原理实现,能够知道隐式返回一个 Promise
回来,这就和 uesEffect
按照返回一个回调来处理清除反作用回调的方式就产生了冲突。而且和 Vue 不一样的是 React 的并无处理 useEffect
中的异步错误,因此在 React 中是不容许在 useEffect
中传入异步回调的。
watchEffect 的实行时机:
flush: 'post'
那将会在 onBeforeMount、 onBeforeUpdate以后注意:Vue 的响应性系统会缓存反作用函数,并异步地刷新它们,这样能够避免同一个“tick” 中多个状态改变致使的没必要要的重复调用。相似于 React 的 setState
只有 reactive
或者 readonly
建立出来的对象使用 isProxy
断定才为 true
。
注意:使用原生的 new Proxy
建立出来的对象,断定为 false
只有源通过被 reactive
被包裹过的才为 true
只有源通过被 readonly
被包裹过的才为 true
默认状况下,provide 提供的数据不是响应式的,但咱们若是须要,可使用 computed
进行处理后再提供出去。
Vue2:
app.component('todo-list', {
//...
provide() {
return {
todoLength: Vue.computed(() => this.todos.length);
}
}
})
复制代码
Vue3:
import { provide, readonly, reactive, ref } from 'vue';
setup() {
const location = ref('North Ploe');
const geolocation = reactive({
longitude: 90,
latitude: 135
});
const updateLocation = () => {
location.value = 'South Pole';
}
// 这里最好使用 readonly 包装后在提供出去,防止 child 直接对其修改
provide('location', readonly(location));
provide('geolocation', readonly(geolocation));
provide('updateLocation', updateLocation);
}
复制代码
$ref 只有在组件渲染(rendered)完成后以后才会进行注入
$ref 不该该在 template 和 computed 中去使用,好比:
// 不容许, 挂载后才会注入 $ref
<template>
<data :data="$ref.child"></data>
</template>
// 不容许
export default {
computed: {
getChild() {
return this.$ref.child;
}
}
}
复制代码
escape hatch 应急紧急方案
顶层错误捕获
app.config.errorHandler = (err, vm, info) => {
console.log(err)
}
复制代码
顶层警告捕获
app.config.warnHandler = function(msg, vm, trace) {
console.log(msg)
}
复制代码
全局配置项,相似 Vue2 的 Vue.prototype.$http = $axios;
用法
app.config.globalProperties.foo = 'bar'
复制代码
这个 Api 的做用在于可以把第三方或者自定义而没有在 Vue 中注册标签使用时,忽略警告。
<template>
<haha-Hello>123</haha-Hello>
</template>
export default {
name: 'hello'
}
复制代码
正常状况下是会报警告的,但这个 Api 就能配置忽略这个警告,标识这是我自定义的组件。
用法:
app.config.isCustomElement = tag => tag.startsWith('haha-')
复制代码
注意:目前这个 Api 是有问题的,请看 girhub issues
这里提供了一些解决方案,Vue 做者尤雨溪也说明了,这个 Api 目前有点问题:
As pointed out, Vue 3 requires configuring custom elements via compiler options if pre-compiling templates.
如前所述,若是是预编译模板,则Vue 3须要经过编译器选项配置自定义元素。
This seems to be now a Vue CLI specific configuration problem so I'm closing it. But feel free to continue the discussion.
如今这彷佛是Vue CLI特定的配置问题,所以我将其关闭。可是请随时继续讨论。
从中提到了,预编译模板(template)使用自定义标签,须要经过编译器选项配置自定义元素,从 girhub issues 中能够看到一个答案,在 vite 上的解决方案:
vite.config.js:
vueCompilerOptions: {
isCustomElement: tag => {
return /^x-/.test(tag)
}
}
复制代码
具体能够看 Vite 的 Api:github vite Api 中的 config 在配置项:config.ts 就能够找到 Vue 编译选项配置字段:vueCompilerOptions
这样配置后就能够忽略上诉例子的警告了:
vueCompilerOptions: {
isCustomElement: tag => {
return /^haha-/.test(tag)
}
}
复制代码
这个 Api 是只针对于 options Api 的,做用是对 mixin
的合并更改策略。
const app = Vue.createApp({
custom: 'hello!'
})
app.config.optionMergeStrategies.custom = (parent, child) => {
console.log(child, parent)
// => "goodbye!", undefined
// => "hello", "goodbye!"
return child || parent
}
app.mixin({
custom: 'goodbye!',
created() {
console.log(this.$options.custom) // => "hello!"
}
})
复制代码
这里能够看到,在 created
输出的时候,输出的是 hello,就是由于设置了合并策略,当组件和 mixin
存在相同属性的时候,会使用 child
的值,当不存在自定义属性重复的时候,当前组件输出的就是 child
由于这时候 parent
为 undefined
www.zhihu.com/question/40… 何时执行 render 函数
Vue2:
<div id="hook-arguments-example" v-demo:[foo].a.b="message"></div>
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
foo: 'HaHa'
message: { color: 'white', text: 'hello!' }
}
})
/*
* name: "demo"
* value: { color: 'white', text: 'hello!' }
* expression: "message"
* argument: "HaHa"
* modifiers: {a: true, b: true}
* name: "tag, data, children, text, elm, ns, context, fnContext, fnOptions, fnScopeId, key, componentOptions, componentInstance, parent, raw, isStatic, isRootInsert, isComment, isCloned, isOnce, asyncFactory, asyncMeta, isAsyncPlaceholder"
**/
复制代码
Vue3:
Vue3.x 和 Vue2.x 的指令在生命周期上有这明显差异,但使用是差很少的
import { createApp } from 'vue'
const app = createApp({})
// register
app.directive('my-directive', {
// called before bound element's attributes or event listeners are applied
created() {},
// called before bound element's parent component is mounted
beforeMount() {},
// called when bound element's parent component is mounted
mounted() {},
// called before the containing component's VNode is updated
beforeUpdate() {},
// called after the containing component's VNode and the VNodes of its children // have updated
updated() {},
// called before the bound element's parent component is unmounted
beforeUnmount() {},
// called when the bound element's parent component is unmounted
unmounted() {}
})
// register (function directive)
app.directive('my-directive', () => {
// this will be called as `mounted` and `updated`
})
// getter, return the directive definition if registered
const myDirective = app.directive('my-directive')
复制代码
app.directive('focus', {
mounted(el) {
el.focus()
}
})
复制代码
dir
就是:
{
mounted(el) {
el.focus()
}
}
复制代码
如何制做插件和使用插件?
请看如下案例:
// 自定义 plug 插件
// myUi.js
import MyButton from './MyButton.vue';
import MyInput from './MyInput.vue';
const componentPool = [
MyButton,
MyInput
];
export default {
install () {
if (options.components) {
option.components.map((compName) => {
componentPool.map((comp) => {
if (compName === comp.name) {
app.component(comp.name, comp);
}
})
})
} else {
componentPool.map(comp => {
app.component(comp.name, comp);
})
}
}
}
复制代码
myUi 该插件,简单的实现了一下按需加载 UI 的方案
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import MyUI from './libs/MyUI';
const app = createApp(App);
app.use(MyUI, {
components: [
'MyButton',
'MyInput'
]
})
复制代码