vue 混入mixins和插件的高级应用__Vue.js__前端
发布于 3 年前 作者 banyungong 1208 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利
// mixin 基本用法
const empty_mixin = {
  filters:{
    empty: value => {
        value || '--'
    }
  },
  created() {
    console.log('注入created');
  }
}

new Vue({
  el: document.querySelector('#app'),
  mixins: [empty_mixin],
  data:{
    user: ''
  },
  template: `<h1>hello, {{ user | empty }}</h1>`
})

作用: 1、可以将多个组件相同的逻辑,抽离出来 缺点: 1、变量来源不明确,不利于阅读 2、mixins可能会造成命名冲突 3、mixins和组件可能会出现多对多的关系,复杂度比较高

其实mixins提供的功能在某些情况下,使用utils也能解决问题,vue只是给开发者提供了一个口子,至于到底用不用,需要视实际情况而定。在简单组件中,可以使用mixins避免cv代码。

基于vue中的合并策略实现一个页面离开的时候的钩子函数。

const originalOptionMergeStrategies = Vue.config.optionMergeStrategies
originalOptionMergeStrategies.exit = originalOptionMergeStrategies.created

const notify = (name, vm) => {
  let lifeCycle = vm.$options[name]
  if(lifeCycle && lifeCycle.length) {
    lifeCycle.forEach(lc =>lc.call(vm))
  }

  const children = vm.$children
  if(children && children.length) {
    children.forEach(child => notify(name, child))
  }
}

const bind = vm => {
  window.addEventListener('visibilitychange', () => {
    if(document.visibilityState === 'hidden') {
      notify('exit', vm)
    }
  })
}

const vm = new Vue({
  el: document.querySelector('#app'),
  template: `<div>hello</div>`,
  exit() {
    alert('leave current page?')
  }
})

bind(vm)

vue中的插件机制的简单应用

const logger = {
  // vue中的插件要求提供一个install方法 install方法有两个参数
  install(Vue, options) {
    Object.defineProperty(Vue.prototype, '$log', {
      value:() => console.log(`打印日志了~`),
      configurable:false,
      enumerable:false,
      writable: false
    })
  }
}

Vue.use(logger)

new Vue({
  el: document.querySelector('#app'),
  template:`<h1>hello world</h1>`,
  created() {
    this.$log()
  }
})

vue中这种插件的机制其实用的还是挺多的。开发者可以利用这点,在开发一套基于自己当前开发项目的自己的组件,然后在项目中use之后直接使用,而无需每次使用的时候都引入。

const Button = {
  name: 'el-button',
  template:`<button @click='onClick'>{{ label }}</button>`,
  props:{
    label: {
      default: '按钮'
    }
  },
  methods:{
    onClick() {
      this.$emit('click')
    }
  }
}

const PUI = [
  Button
]


PUI.install = (Vue, options) => {
  PUI.forEach(({ name, ...component }) => {
    Vue.component(name, component)
  })
}

Vue.use(PUI)

new Vue({
  el: document.querySelector('#app'),
  template: `<div>
      <h1>hello</h1>
      <el-button label='aa' @click='handleClick'></el-button>
    </div>`,
  methods:{
    handleClick() {
      console.log('点击生效了~');
    }
  }
})

其实在vue的官网中也已经提及这种写法

  下面组件基于如下业务场景:当前公司现需开发一个新项目,新项目中会用到原先项目中的组件,但是
  现在问题是原先项目是基于react框架写的,现在就需要在vue项目中使用到react组件。
  其基本思路是这样的:
     1、react组件还是使用reactDOM.render进行渲染,渲染之后的组件放到一个新dom结构中
     2、在vue中使用react组件的时候需要注意的是将vue组件中属性在react组件中透穿。
     3、在react组件中,children有可能又是一个vue组件,所以还需要实现在react组件中渲染一
     个vue组件

// react 里面挂载vue组件
class VueWrapper extends React.Component {
  constructor(props){
    super(props)
  }

  componentWillUnmount() {
    this.vueInstance.$destroy()
  }

  createVueInstance = el => {
    const { component, on, ...props } = this.props

    this.vueInstance = new Vue({
      el,
      data: props,
      render(h) {
        return h('xxx-internal-component', {props: this.$data, on},
          [
            h('yyy-internal-react-wrapper', { props: {
              component: () => React.createElement('div', {}, this.children)
            }})
          ]
        )
      },
      components: {
        'xxx-internal-component': component,
        'yyy-internal-react-wrapper': ReactWrapper
      }
    })

  }

  render() {
    return React.createElement('div', { ref: this.createVueInstance })
  }
}

const makeReactComponent = Component => {
  return class ReactRunInVue extends React.Component {
    static displayName = 'vue-react'
    constructor(props) {
      super(props)
      this.state = {
        ...props
      }
    }

    wrappChildren(children) {
      return {
        render : h => h('div', children)
      }
    }

    render() {
      const { children, ...rest } = this.state
      const wrappedChildren = this.wrappChildren(children)
      return React.createElement(
        Component,
        { ...rest },
        children && React.createElement(VueWrapper, { component: wrappedChildren })

      )
    }
  }
}

// reactWrapper 组件,vue组件
// 本质

const ReactWrapper = {
  props: ['component'],
  render(h){
    return h('div', { ref: 'react' })
  },

  methods: {
    // 渲染react组件到div上边 核心:react要在vue里面跑的核心就是渲染到一个div上
    mountReactComponent(component) {
      const Component = makeReactComponent(component)
      // 处理一下children让react能认识
      const children = this.$slots.default !== void 0 ? { children: this.$slots.default } : {}

      ReactDOM.render(
        React.createElement(
          Component,
          { ...this.$attrs, ...this.$listeners, ...children, ref: ref => (this.reactComponentRef = ref) }
        ),
        this.$refs.react
      )
    },

    mounted() {
      this.mountReactComponent(this.$props.component)
    },

    beforeDestroy() {
      ReactDOM.unmountComponentAtNode(this.$refs.react)
    },

    inheritAttrs: false,
    watch: {
      $attrs: {
        deep: true,
        handler() {
          this.reactComponentRef.setState({...this.$attrs})
        }
      },
      '$props.component': {
        handler(newComponent) {
          this.mountReactComponent(newComponent)
        }
      }
      // ...
    }
  }
}

// 判断是否是react组件 如果是 走特殊逻辑
const isReactComponent = component => {
  return !(
    typeof component === 'function' && 
    component.prototype &&
    (
      (component.prototype.constructor.super && component.prototype.constructor.super.isVue) ||
      component.prototype instanceof Vue
    )
  )
}

// 如果是react组件 处理一下 就是转成 vue组件
// 输入是一个react组件 输出是一个vue组件
const resolver = (component) => {
  return {
    components: {
      ReactWrapper //最终都是在这个组件中完成的
    },
    props: [],
    inheritAttrs: false,
    render(h) {
      return h ('react-wrapper', 
       { 
        props: { component },
        attrs: this.$attrs,
        on: this.$listeners
       }),
       this.$slots.default
    }
  }
}


const ReactRunInVuePlugin = {
  install(Vue, options) {
    // 保留原始的 components合并策略
    const originalComponentOptionMergeStrategies = Vue.config.optionMergeStrategies.components

    Vue.config.optionMergeStrategies.components = ( parent, ...args ) => {
      const mergedComponentOptions = originalComponentOptionMergeStrategies(parent, ...args)

      const wrappedComponents = mergedComponentOptions 
        // 遍历对象
        ? Object.entries(mergedComponentOptions).reduce((acc, [k, v]) => ({
          ...acc,
          [k] : isReactComponent(v) ? resolver(v) : v
        }), {})
        : mergedComponentOptions

      // 对象mixin 一下
      return Object.assign(mergedComponentOptions, wrappedComponents)
    }

    Vue.prototype.constructor.isVue = true
  }
}

// 在vue中使用react插件
class Button extends React.Component {
  render() {
    return React.createElement('div', {}, [
      React.createElement('h2', {}, 
        React.createElement('em', {}, this.props.children)
      )
    ])
  }
}

Vue.use(ReactRunInVuePlugin)

new Vue({
  el: document.querySelector('#app'),
  components: { 'el-button': Button },
  template: `<div>
    <h2>在vue里面使用react组件</h2>
    <el-button>click me</el-button>
  </div>`,
})

在vue项目中全局注册组件的时候,还在一个一个import? 一招教你再也无需import若干个组件了,利用的还是vue插件机制,vue给我们暴露了插件这个口子,至于怎么用,那就是八仙过海各显神通了

const requireComponent = require.context('./', true, /\.vue$/);

const install = (Vue) => {
	
    requireComponent.keys().forEach((fileName) => {

        let config = requireComponent(fileName);

        let componentName = parseVarNameByFilePath(fileName);

        Vue.component(componentName, config.default || config);
    });
};

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 前端phil 原文链接:https://juejin.im/post/7008171384098521119

回到顶部