阿里云主机折上折
  • 微信号
Current Site:Index > Vue's reactive system and design patterns

Vue's reactive system and design patterns

Author:Chuan Chen 阅读数:9269人阅读 分类: JavaScript

Core Principles of Vue's Reactivity System

Vue's reactivity system is based on ES5's Object.defineProperty (Vue 2) or ES6's Proxy (Vue 3). When data changes, the system can automatically update the view components that depend on that data. This mechanism is implemented through the Observer Pattern and consists of three key roles:

  1. Observer: Recursively converts data objects into observable objects
  2. Dep (Dependency): Collects dependencies (Watcher instances)
  3. Watcher: Acts as an observer, triggering callbacks when data changes
// Simplified implementation of Vue 2 reactivity principle
function defineReactive(obj, key, val) {
  const dep = new Dep()
  
  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {
        dep.addSub(Dep.target)
      }
      return val
    },
    set(newVal) {
      if (newVal === val) return
      val = newVal
      dep.notify()
    }
  })
}

class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    this.subs.push(sub)
  }
  notify() {
    this.subs.forEach(sub => sub.update())
  }
}

Pub-Sub Pattern in Vue Applications

Vue's event system is a classic implementation of the publish-subscribe pattern. The $on method subscribes to events, $emit triggers events, and $off cancels subscriptions. This design decouples direct invocation relationships between components.

// Event bus implementation
class EventBus {
  constructor() {
    this.events = {}
  }
  
  $on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = []
    }
    this.events[event].push(callback)
  }
  
  $emit(event, ...args) {
    const callbacks = this.events[event]
    if (callbacks) {
      callbacks.forEach(cb => cb(...args))
    }
  }
  
  $off(event, callback) {
    if (!callback) {
      delete this.events[event]
    } else {
      const callbacks = this.events[event]
      if (callbacks) {
        this.events[event] = callbacks.filter(cb => cb !== callback)
      }
    }
  }
}

Factory Pattern and Vue Components

The Vue component system adopts the factory pattern concept. Components registered via Vue.component are essentially factory functions that create new instances each time the component is used.

// Component factory pattern example
Vue.component('smart-button', {
  props: ['type'],
  render(h) {
    const buttonTypes = {
      primary: 'btn-primary',
      danger: 'btn-danger',
      default: 'btn-default'
    }
    return h('button', {
      class: ['btn', buttonTypes[this.type || 'default']]
    }, this.$slots.default)
  }
})

Strategy Pattern for Form Validation

Vue form validation can be elegantly implemented using the strategy pattern, encapsulating different validation rules as independent strategies.

// Validation strategy object
const validationStrategies = {
  required(value) {
    return value !== undefined && value !== null && value !== ''
  },
  minLength(value, length) {
    return value.length >= length
  },
  email(value) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
  }
}

// Vue validation directive
Vue.directive('validate', {
  bind(el, binding, vnode) {
    const rules = binding.value
    const input = el.querySelector('input')
    
    input.addEventListener('blur', () => {
      const value = input.value
      let isValid = true
      
      for (const [rule, param] of Object.entries(rules)) {
        if (!validationStrategies[rule](value, param)) {
          isValid = false
          break
        }
      }
      
      if (!isValid) {
        el.classList.add('error')
      } else {
        el.classList.remove('error')
      }
    })
  }
})

Proxy Pattern for Data Validation

In Vue 3's Composition API, the proxy pattern can be used to add additional control over reactive data.

import { reactive } from 'vue'

function createValidatedReactiveObject(data, validators) {
  return new Proxy(reactive(data), {
    set(target, key, value) {
      if (validators[key] && !validators[key](value)) {
        console.warn(`Invalid value for ${key}: ${value}`)
        return false
      }
      target[key] = value
      return true
    }
  })
}

// Usage example
const state = createValidatedReactiveObject(
  { age: 25 },
  {
    age: value => value >= 18 && value <= 120
  }
)

Decorator Pattern for Enhancing Vue Functionality

Through Higher-Order Components (HOC), the decorator pattern can be used to extend Vue component functionality.

function withLoading(WrappedComponent) {
  return {
    data() {
      return { isLoading: false }
    },
    methods: {
      async loadData() {
        this.isLoading = true
        try {
          await WrappedComponent.methods.loadData.call(this)
        } finally {
          this.isLoading = false
        }
      }
    },
    render(h) {
      return h('div', [
        this.isLoading ? h('div', 'Loading...') : null,
        h(WrappedComponent, {
          props: this.$attrs,
          on: this.$listeners,
          scopedSlots: this.$scopedSlots
        })
      ])
    }
  }
}

State Pattern for Managing Complex Component States

For complex components with multiple states, the state pattern can significantly improve maintainability.

// Base state class
class FormState {
  constructor(context) {
    this.context = context
  }
  
  next() {}
  previous() {}
}

// Concrete states
class EditingState extends FormState {
  next() {
    this.context.setState(new ValidatingState(this.context))
  }
  
  render() {
    return <EditForm />
  }
}

class ValidatingState extends FormState {
  async next() {
    if (await validate(this.context.data)) {
      this.context.setState(new SubmittingState(this.context))
    }
  }
  
  previous() {
    this.context.setState(new EditingState(this.context))
  }
  
  render() {
    return <ValidatingIndicator />
  }
}

// Usage in Vue component
export default {
  data() {
    return {
      currentState: null
    }
  },
  created() {
    this.setState(new EditingState(this))
  },
  methods: {
    setState(newState) {
      this.currentState = newState
    },
    next() {
      this.currentState.next()
    },
    previous() {
      this.currentState.previous()
    }
  },
  render() {
    return this.currentState.render()
  }
}

Composite Pattern for Building Complex UI Structures

Vue's slot mechanism naturally supports the composite pattern, enabling the construction of complex UI hierarchies.

// Composite component example
Vue.component('tree-node', {
  props: ['node'],
  template: `
    <li>
      <div>{{ node.name }}</div>
      <ul v-if="node.children && node.children.length">
        <tree-node 
          v-for="child in node.children"
          :key="child.id"
          :node="child"
        />
      </ul>
    </li>
  `
})

Vue.component('tree-view', {
  props: ['data'],
  template: `
    <ul class="tree">
      <tree-node :node="data" />
    </ul>
  `
})

Flyweight Pattern for Optimizing List Performance

When rendering large lists, the flyweight pattern can significantly improve performance. Vue's v-for with key already includes this optimization.

// Before optimization
{
  data() {
    return {
      items: Array(1000).fill().map((_, i) => ({ id: i, text: `Item ${i}` }))
    }
  },
  template: `
    <div>
      <div v-for="item in items" :key="item.id">
        {{ item.text }}
      </div>
    </div>
  `
}

// Further optimization using virtual scrolling
import { RecycleScroller } from 'vue-virtual-scroller'

{
  components: { RecycleScroller },
  data() {
    return {
      items: Array(10000).fill().map((_, i) => ({ id: i, text: `Item ${i}` }))
    }
  },
  template: `
    <RecycleScroller
      class="scroller"
      :items="items"
      :item-size="50"
      key-field="id"
    >
      <template v-slot="{ item }">
        <div class="item">
          {{ item.text }}
        </div>
      </template>
    </RecycleScroller>
  `
}

Chain of Responsibility Pattern for Handling Async Operations

Vue's plugin system and middleware mechanism can use the chain of responsibility pattern to handle complex processes.

// Async operation handling chain
class AsyncHandlerChain {
  constructor() {
    this.handlers = []
  }
  
  use(handler) {
    this.handlers.push(handler)
    return this
  }
  
  async execute(context) {
    let index = 0
    const next = async () => {
      if (index < this.handlers.length) {
        const handler = this.handlers[index++]
        await handler(context, next)
      }
    }
    await next()
    return context
  }
}

// Usage in Vue
const apiChain = new AsyncHandlerChain()
  .use(async (ctx, next) => {
    ctx.startTime = Date.now()
    await next()
    ctx.duration = Date.now() - ctx.startTime
  })
  .use(async (ctx, next) => {
    try {
      await next()
    } catch (err) {
      console.error('API error:', err)
      throw err
    }
  })

export default {
  methods: {
    async fetchData() {
      const context = { vm: this }
      return apiChain.execute(context)
    }
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.