Skip to content

前言

vue 中组件复用的方式有 mixin,extend,高阶组件和组合组件三种方式

基础

如何创建一个全局单例组件

const instance = new Vue(options), 通过这种方式能够实现部分命令方式调用组件。

mixin 和 extend 的区别

mixin 是对选项的混合,发生在生成 vue 实例之前,extend 是对 vue 实现继承,返回的是一个 vue 实例, 类似于 Object.create()

HOC 实现一个 promise 组件

代码中经常有请求一个 api,然后根据 api 显示不同逻辑的需求

vue
<template>
  <div>
    <div v-if="loading">loading</div>
    <div v-if="!loading">
      {{ content }}
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      content: "",
      loading: true,
    };
  },
  methods: {
    getContent() {
      this.loading = true;
      this.$api.get("/some").then((content) => {
        this.loading = false;
        this.content = content;
      });
    },
  },
};
</script>

通过 HOC 将该组件的请求逻辑提取出来

js
export const withPromise = (component, promiseFn) => {
  return {
    data() {
      return {
        loading: true,
        result: null,
        error: null,
      };
    },
    mounted() {
      this.loading = true;
      promiseFn()
        .then((data) => {
          this.result = data;
        })
        .catch((error) => {
          this.error = error;
        });
    },
    render(h) {
      const loading = () => h();
      const error = () => h();
      const content = () =>
        h(component, {
          loading: this.loading,
          content: this.content,
        });
      return h("div", null, [
        this.loading ? loading() : null,
        this.error ? error() : null,
        !this.loading && !this.error ? content() : null,
      ]);
    },
  };
};

const newComponent = withPromise(view, getContent);

上面实现还有几个问题没有解决

  1. 请求函数的参数还没有,不能通过子组件自定义
  2. 子组件参数如果有变化,触发新的查询
  3. 外部组件对于子组件的 prop 等参数没有传递到子组件,包括插槽等

第一个文件有两种方式拿到子组件的请求参数,第一个是静态的,通过在子组件选项或者定义上面绑定一个特殊的键值实现,或者将值绑定到子组件的实例上,通过 this.$refs 拿到子组件的实例后获取。

第二个问题需要在子组件上动态的添加 watch 函数,watch 函数监听子组件的查询参数并且触发的回调是父组件的请求函数

第三个问题,直接将 hoc 组件上的$attrs 和 $listeners 绑定到子组件的 props 和 $listeners 中,其中有个 v-bind 绑定的点,v-bind 总是会属性绑定到 props 中,对是否是 props 的处理,都是子组件的关心的,而不是父组件关心的。

js
export const withPromise = (component, promiseFn) => {
  return {
    data() {
      return {
        loading: false,
        content: null,
      };
    },
    render(h) {
      return h(component, {
        props: {
          ...this.attrs,
          loading: this.loading,
          content: this.content,
        },
        on: this.$listeners,
        ref: "component", // 用于在mounted时获取实例
        scopedSlot: this.$scopedSlot
      }, this.$children);  // 注意 $children 和 $slot的区别
    },
    methods: {
      getContent() {
        this.loading = true;
        promiseFn(this.$refs.component.requestParams)
          .then((content) => (this.content = content))
          .finally(() => (this.loading = false));
      },
    },
    mounted() {
      this.getContent();
      this.$refs.component.$watch('requestParams', this.getContent())
    },
  };
};

const newComponent = withPromise(view, getContent)
// <newComponent onClick="xxx">xxxx</newComponent>

其实还有个问题,发现没有,就是还是没法将ref进行传递。 要想获取到子组件需要一直ref来使用。

新增props处理

上面代码中对于props处理在每个HOC组件中都差不多类似。简单一点通过

js
const normalProps = (vm) => {
    return {
        attrs: this.vm.$attrs,
        on: this.vm.$listeners,
        slotScopeds: this.vm.$slotScopeds
    }
}

h(compoennt, {...normalProps(this), props: {}}, this.$children)

组合优于HOC

组合的意识是通过compose的方式将原有的HOC fn3(fn2(fn1))的方式变成compose(fn1, fn2, fn3),将组件作为传递。如果withPromise没有参数,都不需要包装 compose(() => withPromise(), withLog)