Análisis de código fuente de vue3: implementar ranuras

introducción

<< revisión >> periodos pasados

  1. Análisis de código fuente de vue3: paquete acumulativo monsterpo
  2. Análisis de código fuente de vue3: implemente el proceso de montaje de componentes
  3. Análisis de código fuente de vue3: implementa accesorios, emite, maneja eventos, etc.

En este problema, ranura - ranura, dividida en ranuras normales, ranuras con nombre, ranuras con alcance, todo el código fuente por favor vea

cuerpo

Utilice las ranuras en las plantillas de la siguiente manera:

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

El contenido de la plantilla eventualmente se convertirá en un función de procesamiento, y la función de procesamiento llamará a la función h para convertirla en vnode, que se usa en vnode de la siguiente manera:

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

Después de leer el uso básico de las tragamonedas, implementemos una tragamonedas juntos, ¡para que puedas entender el principio de las tragamonedas!

Implementar el uso básico

Donde se usan las ranuras es this.slots, y la propiedad llamada es default, luego slots, y la propiedad llamada es default, luego slots es un objeto, el objeto tiene el nombre de la ranura, si el usuario no pasa, puede ser accedido por defecto.

Casos de prueba

¡Atención! Dado que la prueba es dom, primero debe escribir html, etc., y aquí debe crear primero el nodo correspondiente

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

Las pruebas de este caso han comenzado oficialmente.

 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>'
    )
  })

Análisis de requerimientos

Con el caso de prueba anterior, puede analizar lo siguiente:

  1. La forma en que el componente principal usa el componente secundario para pasar la ranura está en el tercer parámetro de h, y lo que se pasa es un objeto, y el valor valor puede ser un objeto o una matriz
  2. Cuando las ranuras se utilizan en subcomponentes, se obtenido en this.$slots
  3. Y también implementa un método renderSlot, renderSlot es para convertir esto. $slots llama a h en vnode

Resolución de problemas:

  1. Si necesita vincularse a esto, agregue juicio al proxy de la función setupStatefulComponent, pase $slots;
  2. Determine si $slot está en el proxy del componente y luego el proxy necesita unir las ranuras a la integración y vincular el valor a convertir los objetos entrantes en matrices;
  3. El método renderSlot llama a la función h para convertir un dato a vnode

Implementación de codificación


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)
      }
}

Con la codificación anterior, el caso de prueba se puede pasar perfectamente

ranura con nombre

Las ranuras con nombre son, además de múltiples ranuras, y además de las predeterminadas, puede agregar otros nombres, ver casos de prueba

Casos de prueba

 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>')
  })

analizar

A través del caso de prueba anterior, se encontró lo siguiente:

  1. renderSlot pasa el segundo argumento, y luego puede obtener las ranuras para eso

Resolución de problemas

Simplemente pase el segundo parámetro directamente en el renderSlot

codificar


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

Este paso no es relativamente simple, en relación con el frente, solo se llama así, se considera el frente, la parte posterior es cómoda y luego se implementa la ranura del alcance

Ranuras con alcance

La ranura del alcance es esa cada ranura puede pasar datos, y los datos solo son válidos en la ranura actual, ver el caso de prueba

Casos de prueba

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>')

  })

Análisis de requerimientos

A través del caso de prueba anterior, se analizan los siguientes:

  1. Al pasar una ranura, se pasa una función y la función puede tomar los parámetros pasados ​​por el subcomponente
  2. renderSlots puede pasar un tercer parámetro, props, para recibir los parámetros pasados ​​por el componente secundario al componente principal

Resolución de problemas:

  1. Problema 1: solo necesita hacer un juicio cuando pasa la ranura, si es una función, necesita ejecutar la función y pasar los parámetros
  2. Problema 2: también es el juicio del contenido entrante, y la función realiza el procesamiento de parámetros entrantes

codificar



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

¡En este punto, todo el caso de prueba puede pasar perfectamente!

Se el primero en comentar

Deje un comentario

Tu dirección de correo electrónico no será publicada.


*