最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue 3.0 新特性与使用 三

    正文概述 掘金(Des bisous)   2021-01-12   651

    该文章重点来梳理一些重要但隐晦不经人注意的知识点!

    watchEffect && watch

    watchEffect 的特征在 watch 保持一致,所以这里仅仅从 watchEffect 出发点梳理即可

    watchEffect 组件初始化的时候会执行一次,组件卸载的时候会执行一次

    watchEffect 可以返回一个句柄 stop,再次调用将可以进行注销 watchEffect

    const stop = watchEffect(() => {
      /* ... */
    })
    
    // later
    stop()
    

    有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。 watchEffect 函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:

    • 副作用即将重新执行时
    • 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)
    watchEffect(onInvalidate => {
      const token = performAsyncOperation(id.value)
      onInvalidate(() => {
        // id has changed or watcher is stopped.
        // invalidate previously pending async operation
        token.cancel()
      })
    })
    

    重点:

    Vue 为什么采用通过传入一个函数去注册副作用清除回调,而不是从回调返回它(react useEffect)?

    Vue 的回答是因为返回值对于异步错误处理很重要。

    我们分别来看看 Vue 和 React 的区别:

    Vue

    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 必须要在 Promiseresolve 之前被注册。

    相比较于 React 的写法,因为 React 清理副作用的方法是采用 return 一个回调出来,按照这种机制,如果我们在 useEffect 函数中传入 async/await 函数,我们根据对 async/await 的原理实现,可以知道隐式返回一个 Promise 回来,这就和 uesEffect 按照返回一个回调来处理清除副作用回调的方式就产生了冲突。并且和 Vue 不同的是 React 的并没有处理 useEffect 中的异步错误,所以在 React 中是不允许在 useEffect 中传入异步回调的。

    watchEffect 的实行时机:

    • 会在初始运行时同步执行(onBeforeMount之前)
    • 更改观察的 state 时,将在组件更新(onBeforeUpdate)前执行副作用
    • 如果增加了 flush: 'post' 那将会在 onBeforeMount、 onBeforeUpdate之后

    注意:Vue 的响应性系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个“tick” 中多个状态改变导致的不必要的重复调用。类似于 React 的 setState

    isProxy

    只有 reactive 或者 readonly 创建出来的对象使用 isProxy 判定才为 true

    注意:使用原生的 new Proxy 创建出来的对象,判定为 false

    isReactive

    只有源经过被 reactive 被包裹过的才为 true

    isReadonly

    只有源经过被 readonly 被包裹过的才为 true

    provide/inject

    默认情况下,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

    $ref 只有在组件渲染(rendered)完成后之后才会进行注入

    $ref 不应该在 template 和 computed 中去使用,比如:

    // 不允许, 挂载后才会注入 $ref
    <template>
      <data :data="$ref.child"></data>
    </template>
    
    // 不允许
    export default {
        computed: {
            getChild() {
                return this.$ref.child;
            }
        }
    }
    

    escape hatch 应急紧急方案

    Application Config

    errorHandler

    顶层错误捕获

    app.config.errorHandler = (err, vm, info) => {
        console.log(err)
    }
    

    warnHandler

    顶层警告捕获

    app.config.warnHandler = function(msg, vm, trace) {
       console.log(msg)
    }
    

    globalProperties

    全局配置项,类似 Vue2 的 Vue.prototype.$http = $axios; 用法

    app.config.globalProperties.foo = 'bar'
    

    isCustomElement

    这个 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 目前有点问题:

    从中提到了,预编译模板(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)
        }
    }
    

    optionMergeStrategies

    这个 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 函数

    Directive

    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')
    
    • instance: 使用指令的组件实例。
    • value: 传递给指令的值。例如,在v-my-directive =“ 1 + 1”中,该值为2。
    • oldValue: 旧的值,仅在 beforeUpdate 和更新时可用。值是否已更改都可用。
    • arg: 参数传递给指令(如果有)。例如,在 v-my-directive:foo 中,arg 为“ foo”。
    • modifiers: 包含修饰符(如果有)的对象。例如,在v-my-directive.foo.bar 中,修饰符对象为 {foo:true,bar:true}。
    • dir: 一个对象,在注册指令时作为参数传递。例如,在指令中
    app.directive('focus', {
      mounted(el) {
        el.focus()
      }
    })
    

    dir就是:

    {
      mounted(el) {
        el.focus()
      }
    }
    

    use 和 plug

    如何制作插件和使用插件?

    请看以下案例:

    // 自定义 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'
        ]
    })
    

    起源地下载网 » Vue 3.0 新特性与使用 三

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元