Анализ исходного кода vue3 — реализация слотов

введение

<< обзор >> прошлые периоды

  1. Анализ исходного кода vue3 — накопительный пакет monsterpo
  2. Анализ исходного кода vue3 — реализация процесса монтирования компонентов
  3. Анализ исходного кода vue3 — реализует реквизиты, испускание, обработку событий и т. д.

В этом выпуске, слот — слот, разделенный на обычные слоты, именованные слоты, слоты с областью действия, весь исходный код см.

тело

Используйте слоты в шаблонах следующим образом:

<todo-button>
  Add todo
</todo-button>

Содержимое шаблона в конечном итоге будет преобразовано в функция рендеринга, а функция рендеринга вызовет функцию h для преобразования в vnode, который используется в vnode следующим образом:

render() {
    return h(TodoButton, {}, this.$slots.default)
  },

Прочитав основы использования слотов, давайте вместе реализуем слоты, чтобы вы могли понять принцип работы слотов!

Реализовать базовое использование

Где используются слоты это this.slots, и вызываемое свойство по умолчанию, затем слоты, и вызываемое свойство по умолчанию, затем слоты это объект, объект имеет имя слота, если пользователь не проходит, он может быть доступным по умолчанию.

Испытательные случаи

внимание!!! Так как тест dom, вам нужно сначала написать html и т.д., а здесь вам нужно сначала создать соответствующий узел

 let appElement: Element;
  beforeEach(() => {
    appElement = document.createElement('div');
    appElement.id = 'app';
    document.body.appendChild(appElement);
  })
  afterEach(() => {
    document.body.innerHTML = '';
  })

Тестирование этого дела официально началось

 test('test basic slots', () => {
    const Foo = {
      name: 'Foo',
      render() {
        return h('div', { class: 'foo' }, [h('p', {}, this.count), renderSlots(this.$slots)]);
      }
    }

    const app = createApp({
      render() {
        return h('div', { class: 'container' }, [
          h(Foo, { count: 1 }, { default: h('div', { class: 'slot' }, 'slot1') }),
          h(Foo, { count: 2 }, { default: [h('p', { class: 'slot' }, 'slot2'), h('p', { class: 'slot' }, 'slot2')] }),
        ])
      }
    })

    const appDoc = document.querySelector('#app')
    app.mount(appDoc);
    const container = document.querySelector('.container') as HTMLElement;
    expect(container.innerHTML).toBe('<div class="foo"><p>1</p><div><div class="slot">slot1</div></div></div><div class="foo"><p>2</p><div><p class="slot">slot2</p><p class="slot">slot2</p></div></div>'
    )
  })

Анализ требований

С помощью приведенного выше тестового примера вы можете проанализировать следующее:

  1. Способ, которым родительский компонент использует дочерний компонент для передачи в слот, описан в третий параметр h, и то, что передается, является объектом, а значение value может быть объектом или массивом
  2. Когда слоты используются в подкомпонентах, они получено в этом слоте. $
  3. А также реализует метод renderSlot, renderSlot должен превратить вызов this.$slots h в vnode

Решение проблем:

  1. Если вам нужно привязаться к этому, добавьте суждение к прокси-серверу функции setupStatefulComponent, передайте $slots;
  2. Определите, находится ли $slot в прокси компонента, а затем прокси необходимо привязать слоты к интеграции и привязать значение к преобразовать входящие объекты в массивы;
  3. Метод renderSlot вызывает функцию h для преобразования фрагмента данных в vnode.

Реализация кодирования


function setupStatefulComponent(instance: any) {
  instance.proxy = new Proxy({  }, {
      get(target,key){
       else if(key in instance.slots){
         return instance.slots[key]
       }
      }
  })
}
export function setupComponent(instance) {
  const { props, children } = instance.vnode
  const slots = {}
  for (const key in children) {
      slots[key] = Array.isArray(children[key]) ? children[key] : [children[key]]
  }
  instance.slots = slots
  }
  export function renderSlots(slots) {
    const slot = slots['default']
       if (slot) {
        return createVNode('div', {}, slot)
      }
}

С приведенным выше кодом тестовый пример может быть успешно пройден.

Именованный слот

Именованные слоты есть, помимо нескольких слотов, и кроме стандартных, можно добавить другие имена, см. тестовые примеры

Испытательные случаи

 test('slots test', () => {
    const Foo = {
      name: 'Foo',
      render() {
        return h('div', { class: 'foo' },
          [
            renderSlots(this.$slots, 'header'),
            h('div', { class: 'default' }, 'default'),
            renderSlots(this.$slots, 'footer')
          ]
        );
      }
    }

    const app = createApp({
      name: 'App',
      render() {
        return h('div', { class: 'container' }, [h(Foo, {}, {
          header: h('h1', {}, 'header'),
          footer: h('p', {}, 'footer')
        })])
      }
    })

    const appDoc = document.querySelector('#app')
    app.mount(appDoc);

    const container = document.querySelector('.container') as HTMLElement

    expect(container.innerHTML).toBe('<div class=\"foo\"><div><h1>header</h1></div><div class=\"default\">default</div><div><p>footer</p></div></div>')
  })

анализировать

В приведенном выше тестовом примере было обнаружено следующее:

  1. renderSlot передает второй аргумент, и тогда вы можете получить слоты для этого

Решение проблем

Просто передайте второй параметр прямо в renderSlot.

шифровать


  export function renderSlots(slots, name = 'default') {
    const slot = slots[name]
       if (slot) {
        return createVNode('div', {}, slot)
      }
}

Этот шаг не относительно простой, относительно передней части, просто так называемая, передняя часть считается, задняя часть удобна, а затем реализуется слот для прицела.

Слоты с ограниченной областью действия

Слот области видимости каждый слот может передавать данные, и данные действительны только в текущем слоте, см. тестовый пример

Испытательные случаи

test('slots test', () => {
    const Foo = {
      name: 'Foo',
      render() {
        return h('div', { class: 'foo' },
          [
            renderSlots(this.$slots, 'header', { children: 'foo' }),
            h('div', { class: 'default' }, 'default'),
            renderSlots(this.$slots, 'footer')
          ]
        );
      }
    }

    const app = createApp({
      name: 'App',
      render() {
        return h('div', { class: 'container' }, [h(Foo, {}, {
          header: ({ children }) => h('h1', {}, 'header ' + children),
          footer: h('p', {}, 'footer')
        })])
      }
    })

    const appDoc = document.querySelector('#app')
    app.mount(appDoc);

    const container = document.querySelector('.container') as HTMLElement

    expect(container.innerHTML).toBe('<div class=\"foo\"><div><h1>header foo</h1></div><div class=\"default\">default</div><div><p>footer</p></div></div>')

  })

Анализ требований

В приведенном выше тестовом примере анализируется следующее:

  1. При передаче в слоте передается функция, и функция может принимать параметры, переданные подкомпонентом
  2. renderSlots может передать третий параметр, props, чтобы получить параметры, переданные дочерним компонентом родительскому компоненту.

Решение проблем:

  1. Проблема 1: вам нужно только принять решение, когда вы передаете слот, если это функция, вам нужно выполнить функцию и передать параметры
  2. Проблема 2: Это также оценка входящего контента, и функция выполняет обработку входящего параметра

шифровать



export function renderSlots(slots, name = 'default', props = {}) {
  const slot = slots[name];

  if (slot) {
    if (isFunction(slot)) {
      return createVNode('div', {}, slot(props))
    }
    return createVNode('div', {}, slot)
  }
}


 const slots = {}
  for (const key in children) {
    if (isFunction(children[key])) {
      slots[key] = (props) => Array.isArray(children[key](props)) ? children[key](props) : [children[key](props)]
    } else {
      slots[key] = Array.isArray(children[key]) ? children[key] : [children[key]]
    }
  }

  instance.slots = slots

На этом этапе весь тестовый пример может быть пройден идеально!

Оставь первый комментарий

Оставьте комментарий

Ваш электронный адрес не будет опубликован.


*