Vue 单例组件

类似弹窗等组件我们希望是全局单例的,下面是 Vue 中的几种实现方式。

Vue 实例

使用Vue.extend方法得到一个组件的实例,挂载之后插入到DOM中。

var MyComponent = Vue.extend({
  template: '<div v-on:click="world">Hello {{msg}}!</div>',
  props: {
    msg: {
      type: String,
      required: true
    }
  }
  methods : {
    world : function() {
      console.log('world')
    }
  }
})
 
var component = new MyComponent(propsData: {msg: 'world'}).$mount()
document.getElementById('app').appendChild(component.$el)

传入prop需要在 new 组件时传入一个名为propsData的对象。组件内的data可以直接以component.data['name']这种形式修改。

Vue实例组件生命周期还有$forceUpdate()$nextTick()$destroy()方法。
注意:$forceUpdate()不会影响所有子组件,只影响实例本身和插入插槽内容的子组件。

Vue 全局 mixin

Vue.mixin({
    mounted:function(){
        if(this.$root===this){
            var ne=document.createElement("div");
            ne.id="placeholderGLOBAL";
            this.$el.appendChild(ne);
            var dp=Vue.component("global-components");
            var idp=new dp({parent:this,el:"#placeholderGLOBAL"});
            idp.$mount();
        }
    }
});

https://forum.vuejs.org/t/solved-component-singleton-render-only-once-reuse-dom-elements/17799/9

使用SVG Patterns作为平铺背景

<svg width="100%" height="100%">
    <defs>
        <pattern id="polka-dots" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse">
             
            <circle fill="#bee9e8" cx="50" cy="50" r="25">
            </circle>
             
        </pattern>
    </defs>
     
    <rect x="0" y="0" width="100%" height="100%" fill="url(#polka-dots)"></rect>
</svg>

defs标签内定义一个pattern并提供一个id,让后将fill属性指向该ID的URL : fill="url(#polka-dots)"。
效果如下:

更多的属性可以在这里尝试

对比传统的css平铺

😡CSS平铺缺点:

  • 与位图一起使用时,它不可扩展
  • 性能较低
  • 更难以定制
  • 仅限于矩形重复

😀SVG模式专业人士:

  • 轻量级
  • 从CSS自定义
  • 可扩展
  • 能够创建复杂的模式

http://www.heropatterns.com/

⌚️Intl.DateTimeFormat.format 与 Date.toLocaleString

两种方法使用方法基本一致,第一个参数可以接受BCP 47 标准的字符串,或者一个包含区域,时间格式的字符串数组。
第二个参数接受一个格式化的配置项,包含时间样式,时区,格式匹配器。下面是一个格式化utc时间戳的例子。

const DateTimeFormat = new Intl.DateTimeFormat('zh-CN', {
    year: 'numeric', month: 'numeric', day: 'numeric',
    hour: 'numeric', minute: 'numeric', second: 'numeric',
    hour12: false,
});
function formatTime(utc) {
    return DateTimeFormat.format(utc);
}

formatTime(new Date)
// "2019/8/24 02:29:38"

Intl.DateTimeFormat.prototype.format 不需要每次都设置区域设置和选项,所以它的性能更好,但是Intl是ecma402中添加的内容,兼容没有toLocaleString好。

在Vue组件可见时异步加载

对于单页应用,我们通常用路由来做代码分割,但在一个页面,对于一个首屏不可见的组件或者说需要用户交互才会触发显示的组件,我们还可以用延迟加载组件的方法,来减少首页体积,更细粒度的控制加载内容。vue 提供了 asyncComponent 工厂函数,我们可以在这个基础上做可见时异步加载。

const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})

上面为文档上的例子,我们需要在其基础上进行扩展,首先timeout不能指定时间,因为如果组件不可见的话一直不会加载。此外由于组件为异步加载,所以我们不能直接判断组件可见,但是我们可以通过异步组件的loading是否出现在视窗内来判断。component参数接受一个Promise对象,所以这里我们可以直接构造一个promise,然后保存其resolve函数,然后在loading组件挂载后使用intersectionObserver api监听其是否出现在视窗内,在intersectionObserver回调函数中使用保存的resolve函数,将 componentpending 变为 resolved状态。

export default function lazyLoadComponent({
  componentFactory,
  loading,
  loadingData,
}) {
  let resolveComponent;

  return () => ({
    component: new Promise((resolve) => {
      resolveComponent = resolve;
    }),
    loading: {
      mounted() {
        const observer = new IntersectionObserver(([entry]) => {
          if (!entry.isIntersecting) return;
          observer.unobserve(this.$el);
          componentFactory().then(resolveComponent);
        });
        observer.observe(this.$el);
      },
      render(h) {
        return h(loading, loadingData);
      },
    },
  });
}

示例

Foo: lazyLoadComponent({
   componentFactory: () => import(`./components/Foo.vue`),
}),

在nuxt中使用,我们需要借助Vue动态组件方式来延迟加载。

<template>
    <div class="post">
        <div style="height: 110vh"></div>
        <div :is="content"></div>
    </div>
</template>

<script>
    export default {
        components: {},
        data() {
            return {content: ''}
        },
        mounted() {
            this.content = this.$loadComponent(() => import(`...`))
        }
    }
</script>

参考文章

Lazy Load Vue.js Components When They Become Visible

前端状态管理——Redux与有限状态机(FSM)

Redux

Redux和Vuex都是基于Flux的思想实现的,这里我们以Redux为例。

核心概念

  • 所有的状态存放在 Store。组件每次重新渲染,都必须由状态变化引起。
  • 用户在 UI 上发出 action。
  • 纯函数 reducer 接收 action,然后根据当前的 state,计算出新的 state。

特点

数据的单一来源:即整个APP尽量不使用内部state,所有的状态全部源自于Redux
纯函数reducer:处理action并生成新的state,其形式为 newState = f(state,action),不允许任何副作用
不可变state:只有当state对象被整体替换时候,才会触发view层的更新。

副作用

在函数式编程中,最大的特点就是纯函数,一个输入一个结果,这个过程想数学函数一样,透明可控,但是异步操作等给应用程序带来了不确定性和可变性,这种被称为副作用。直接用Redux处理这些副作用比较复杂也难以测试,所以出现了redux-saga,mobx等工具。

有限状态机(FSM)

有限状态机(finite-state machine),是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
比如TCP连接,Promise都可以用状态机来表示他们工作方式。
捕获.PNG

特点

  • 状态总数是有限的。
  • 任一时刻,只处在一种状态之中。
  • 某种条件下,会从一种状态转变(transition)到另一种状态。

与有限状态机差异

  • 都是一个状态容器,但FSM将将有限状态与无限状态或者上下文状态区分开,也正因如此有限状态机可以可视化状态之间的转换
  • redux 的 reducer 就是一个函数,而状态机是“带规则的reducer” ,你可以定义由于事件导致的有限状态之间的合法转换,以及应该在转换中(或在状态的进入/退出时)执行哪些操作
  • redux 没有内置处理副作用
  • Redux 需要使用单一的全局原子性的 Store,状态机使用类似角色模型的方式,其中可以存在许多彼此通信的分层状态图。
  • Redux 单向数据流动,状态机并没约束数据流。
  • 状态机可以适应任何框架

如何选择

关键要看你主要关注应用的最终状态还是约束状态变化状态转换的过程

参考文章:

What is an actual difference between redux and a state machine (e.g. xstate)?