Практика работы с библиотекой кросс-рамных компонентов Taro Next H5

JavaScript Web Components
Практика работы с библиотекой кросс-рамных компонентов Taro Next H5

Автор: Bump Man - JJ

TaroЭто многоцелевая среда разработки. Разработчикам нужно написать только один код для создания приложений для каждого апплета, H5 и React Native.

Taro NextБета-версия была выпущена недавно, и поддержка мини-программ и H5 была полностью улучшена. Добро пожаловать!

задний план

Taro Next будет поддерживать разработку с использованием нескольких фреймворков

В прошлом Taro 1 и Taro 2 можно было разрабатывать только с использованием синтаксиса React, но фреймворк Taro следующего поколения реализовал всю архитектуру.Обновить, который поддерживает разработку мультитерминальных приложений с использованием таких фреймворков, как React, Vue и Nerv.

Чтобы поддерживать использование нескольких платформ для разработки, Taro необходимо трансформировать свои собственные сквозные возможности адаптации. В этой статье речь пойдет оБиблиотека боковых компонентов Taro H5ремонтные работы.

Taro H5

Taro следует спецификациям компонентов и API, в основном основанных на мини-программах WeChat и дополненных другими мини-программами.

Однако браузер не имеет компонентов и API спецификации апплета для использования.Например, мы не можем использоватьviewкомпоненты иgetSystemInfoAPI. Поэтому нам необходимо реализовать набор библиотек компонентов и библиотек API на основе спецификации апплета на стороне H5.

Taro H5 架构图

В Taro 1 и Taro 2 библиотека компонентов Taro H5 была разработана с использованием синтаксиса React. Но если разработчики используют Vue для разработки приложений H5 в Taro Next, он не будет совместим с существующей библиотекой компонентов H5.

Таким образом, основными вопросами, которые необходимо решить в данной работе, являются:Нам нужно реализовать библиотеку компонентов, которая может использоваться такими фреймворками, как React и Vue, на стороне H5..

выбор плана

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

Но, учитывая следующие два момента, мы отказались от этой идеи:

  1. Недостаточная ремонтопригодность и расширяемость библиотеки компонентов. Всякий раз, когда необходимо исправить ошибку или добавить новую функцию, нам необходимо обновить версии React и Vue библиотеки компонентов соответственно.
  2. Цель Taro Next — поддерживать разработку мультитерминальных приложений с использованием любой платформы. Если использование фреймворков, таких как Angular, будет поддерживаться для разработки в будущем, нам необходимо разработать библиотеки компонентов, соответствующие таким фреймворкам, как Angular.

Итак, есть ли решение сделать библиотеку компонентов, построенную только из одного кода, совместимой со всеми фреймворками веб-разработки?

ответWeb Components.

Однако процесс преобразования библиотеки компонентов в веб-компоненты не был гладким, и мы также столкнулись с множеством проблем, поэтому эта статья расскажет вам обо всем этом.

Введение в веб-компоненты

Web ComponentsОн состоит из ряда технических спецификаций, которые позволяют разработчикам разрабатывать компоненты, изначально поддерживаемые браузерами.

технические характеристики

Основные технические характеристики веб-компонентов:

  • Custom Elements
  • Shadow DOM
  • HTML Template

Пользовательские элементы позволяют разработчикам настраивать теги HTML с определенным поведением.

Shadow DOM заключает структуру и стили в теги.

<template>Этикетка обеспечивает мультиплексируемость для веб-компонентов или сотрудничает<slot>Этикетки обеспечивают гибкость.

Пример

Определите шаблон:

<template id="template">
  <h1>Hello World!</h1>
</template>

Создать пользовательский элемент:

class App extends HTMLElement {
  constructor () {
    super(...arguments)

    // 开启 Shadow DOM
    const shadowRoot = this.attachShadow({ mode: 'open' })

    // 复用 <template> 定义好的结构
    const template = document.querySelector('#template')
    const node = template.content.cloneNode(true)
    shadowRoot.appendChild(node)
  }
}
window.customElements.define('my-app', App)

использовать:

<my-app></my-app>

Stencil

Использование собственного синтаксиса для написания веб-компонентов довольно громоздко, поэтому нам нужна структура, которая поможет нам повысить эффективность разработки и опыт разработки.

Уже много взрослыхФреймворк веб-компонентов, после некоторого сравнения мы наконец выбралиStencil, по двум причинам:

  1. Созданный командой Ionic, Stencil используется для создания библиотеки компонентов Ionic, проверенной в отрасли.
  2. Stencil поддерживает JSX, что может снизить стоимость миграции существующих библиотек компонентов.

Stencil — это компилятор, который может генерировать веб-компоненты. Он сочетает в себе некоторые отличные концепции интерфейсных фреймворков в отрасли, такие как поддержка Typescript, JSX, виртуальный DOM и т. д.

Пример:

Создайте компонент трафарета:

import { Component, Prop, State, h } from '@stencil/core'

@Component({
  tag: 'my-component'
})
export class MyComponent {
  @Prop() first = ''
  @State() last = 'JS'

  componentDidLoad () {
    console.log('load')
  }

  render () {
    return (
      <div>
        Hello, my name is {this.first} {this.last}
      </div>
    )
  }
}

Используйте компоненты:

<my-component first='Taro' />

Использование Stencil с React и Vue

Пока все хорошо: веб-компоненты написаны на Stencil, т.е. их можно использовать напрямую в React и Vue.

Но на практике возникают некоторые проблемы.Custom Elements EverywhereС помощью серии тестов перечислены проблемы совместимости и связанные с ними проблемы интерфейсных фреймворков в отрасли с веб-компонентами. Ниже кратко представлена ​​совместимость библиотеки компонентов Taro H5 для React и Vue соответственно.

Совместимость с Реактом

1. Props

1.1 Вопрос

Реагировать используетsetAttributeПередайте параметры веб-компонентам в виде . Это работает, когда параметр является примитивным типом, но если параметр является объектом или массивом, поскольку значение атрибута элемента HTML может быть только строкой или нулевым значением, окончательный атрибут, установленный для веб-компонентов, будетattr="[object Object]".

атрибуты и свойстваразница

1.2 Решения

использоватьDOM Propertyспособ передачи параметров.

Мы можем обернуть веб-компоненты слоем компонентов более высокого порядка и установить реквизиты для компонентов более высокого порядка как свойства веб-компонентов:

const reactifyWebComponent = WC => {
  return class extends React.Component {
    ref = React.createRef()

    update () {
      Object.entries(this.props).forEach(([prop, val]) => {
        if (prop === 'children' || prop === 'dangerouslySetInnerHTML') {
          return
        }
        if (prop === 'style' && val && typeof val === 'object') {
          for (const key in val) {
            this.ref.current.style[key] = val[key]
          }
          return
        }
        this.ref.current[prop] = val
      })
    }

    componentDidUpdate () {
      this.update()
    }

    componentDidMount () {
      this.update()
    }

    render () {
      const { children, dangerouslySetInnerHTML } = this.props
      return React.createElement(WC, {
        ref: this.ref,
        dangerouslySetInnerHTML
      }, children)
    }
  }
}

const MyComponent = reactifyWebComponent('my-component')

Уведомление:

  • Дочерние атрибуты и атрибутыhazardlySetInnerHTML должны быть пропущены.
  • Значение атрибута стиля в React может принимать форму объекта, что требует дополнительной обработки.

2. Events

2.1 Вопрос

Потому что реагировать имеет наборСинтетическая система событий, поэтому он не может прослушивать пользовательские события, создаваемые веб-компонентами.

Обратный вызов onLongPress следующего веб-компонента не будет запущен:

<my-view onLongPress={onLongPress}>view</my-view>
2.2 Решения

Получить элемент веб-компонента по ссылке, вручнуюaddEventListenerПривязать события.

Преобразуйте вышеуказанный компонент более высокого порядка:

const reactifyWebComponent = WC => {
  return class Index extends React.Component {
    ref = React.createRef()
    eventHandlers = []

    update () {
      this.clearEventHandlers()

      Object.entries(this.props).forEach(([prop, val]) => {
        if (typeof val === 'function' && prop.match(/^on[A-Z]/)) {
          const event = prop.substr(2).toLowerCase()
          this.eventHandlers.push([event, val])
          return this.ref.current.addEventListener(event, val)
        }

        ...
      })
    }

    clearEventHandlers () {
      this.eventHandlers.forEach(([event, handler]) => {
        this.ref.current.removeEventListener(event, handler)
      })
      this.eventHandlers = []
    }

    componentWillUnmount () {
      this.clearEventHandlers()
    }

    ...
  }
}

3. Ref

3.1 Вопрос

Чтобы решить проблему реквизитов и событий, мы ввели компоненты более высокого порядка. Затем, когда разработчик передает ссылку компоненту более высокого порядка, он на самом деле получает компонент более высокого порядка, но мы надеемся, что разработчик сможет получить соответствующий веб-компонент.

домРеф получитMyComponent, вместо<my-component></my-component>

<MyComponent ref={domRef} />
3.2 Решения

использоватьforwardRefПройти исх.

Преобразуйте вышеуказанный компонент более высокого порядка в форму forwardRef:

const reactifyWebComponent = WC => {
  class Index extends React.Component {
    ...

    render () {
      const { children, forwardRef } = this.props
      return React.createElement(WC, {
        ref: forwardRef
      }, children)
    }
  }
  return React.forwardRef((props, ref) => (
    React.createElement(Index, { ...props, forwardRef: ref })
  ))
}

4. Host's className

4.1 Вопрос

В Stencil мы можем использовать компонент Host, чтобы добавить имя класса к элементу host.

import { Component, Host, h } from '@stencil/core';

@Component({
  tag: 'todo-list'
})
export class TodoList {
  render () {
    return (
      <Host class='todo-list'>
        <div>todo</div>
      </Host>
    )
  }
}

затем используя<todo-list>Элемент будет отображать наше встроенное имя класса «todo-list» и имя класса «Hydrated», автоматически добавленное Stencil:

Но если мы установим динамическое имя класса при использовании, например:<todo-list class={this.state.cls}>. Затем, когда имя динамического класса будет обновлено, встроенные имена классов «список дел» и «гидратированный» будут стерты.

Относительно имени класса «гидратированный»:

Stencil добавляет ко всем веб-компонентамvisibility: hidden;стиль. Затем добавьте имя класса «гидратированный» после инициализации каждого веб-компонента и добавьте имя класса «гидратированный» вvisibilityизменить наinherit. Если «гидратированный» стерт, веб-компоненты не будут видны.

Поэтому нам необходимо убедиться, что имя встроенного класса веб-компонентов не перезаписывается при обновлении имени класса.

4.2 Решения

Компоненты более высокого порядка объединяют встроенные классы при использовании ref для установки свойства className веб-компонента.

Преобразуйте вышеуказанный компонент более высокого порядка:

const reactifyWebComponent = WC => {
  class Index extends React.Component {
    update (prevProps) {
      Object.entries(this.props).forEach(([prop, val]) => {
        if (prop.toLowerCase() === 'classname') {
          this.ref.current.className = prevProps
            // getClassName 在保留内置类名的情况下,返回最新的类名
            ? getClassName(this.ref.current, prevProps, this.props)
            : val
          return
        }

        ...
      })
    }

    componentDidUpdate (prevProps) {
      this.update(prevProps)
    }

    componentDidMount () {
      this.update()
    }

    ...
  }
  return React.forwardRef((props, ref) => (
    React.createElement(Index, { ...props, forwardRef: ref })
  ))
}

Совместимость с Вью

В отличие от React, хотя Vue также использует параметры при передаче параметров в веб-компоненты.setAttributeобразом, но директива v-bind обеспечивает.propМодификатор, который может связывать параметры как свойства DOM. Кроме того, Vue также может прослушивать пользовательские события, создаваемые веб-компонентами.

Поэтому Vue не нуждается в дополнительной обработке по Props и Events, но есть некоторые проблемы совместимости со Stencil, основные три пункта будут перечислены далее.

1. Host's className

1.1 Вопрос

Совместимо с React, часть 4 выше, обновление класса элемента хоста в Vue также переопределит встроенный класс.

1.2 Решения

Точно так же вам нужно обернуть слой пользовательских компонентов Vue в веб-компоненты.

function createComponent (name, classNames = []) {
  return {
    name,
    computed: {
      listeners () {
        return { ...this.$listeners }
      }
    },
    render (createElement) {
      return createElement(name, {
        class: ['hydrated', ...classNames],
        on: this.listeners
      }, this.$slots.default)
    }
  }
}

Vue.component('todo-list', createComponent('todo-list', ['todo-list']))

Уведомление:

  • Мы повторили имя встроенного класса, которое веб-компонент должен иметь в пользовательском компоненте. Когда последующие разработчики установят имя класса для пользовательского компонента, VueАвтоматически объединять имена классов.
  • Вам нужно передать события, связанные с пользовательским компонентом, через$listenersПереход к веб-компоненту.

2. Ref

2.1 Вопрос

Чтобы решить проблему 1, мы оборачиваем слой пользовательских компонентов для веб-компонентов в Vue. Точно так же, когда разработчик использует ссылку, он получает настраиваемый компонент, а не веб-компонент.

2.2 Решения

В Vue нет понятия forwardRef, его можно только модифицировать просто и грубоthis.$parent.$refs.

Добавьте миксин для пользовательского компонента:

export const refs = {
  mounted () {
    if (Object.keys(this.$parent.$refs).length) {
      const refs = this.$parent.$refs

      for (const key in refs) {
        if (refs[key] === this) {
          refs[key] = this.$el
          break
        }
      }
    }
  },
  beforeDestroy () {
    if (Object.keys(this.$parent.$refs).length) {
      const refs = this.$parent.$refs

      for (const key in refs) {
        if (refs[key] === this.$el) {
          refs[key] = null
          break
        }
      }
    }
  }
}

Уведомление:

  • Приведенный выше код не имеет отношения к ссылке на цикл, и ссылку на цикл необходимо оценивать и обрабатывать отдельно.

3. v-model

3.1 Вопрос

Мы используем функцию рендеринга в пользовательском компоненте для рендеринга, поэтому для компонента формы требуется дополнительная обработка.v-model.

3.2 Решения

Используйте пользовательский компонент наmodelопции, использование пользовательских компонентовv-modelопора и событие того времени.

Модифицируйте вышеуказанный пользовательский компонент:

export default function createFormsComponent (name, event, modelValue = 'value', classNames = []) {
  return {
    name,
    computed: {
      listeners () {
        return { ...this.$listeners }
      }
    },
    model: {
      prop: modelValue,
      event: 'model'
    },
    methods: {
      input (e) {
        this.$emit('input', e)
        this.$emit('model', e.target.value)
      },
      change (e) {
        this.$emit('change', e)
        this.$emit('model', e.target.value)
      }
    },
    render (createElement) {
      return createElement(name, {
        class: ['hydrated', ...classNames],
        on: {
          ...this.listeners,
          [event]: this[event]
        }
      }, this.$slots.default)
    }
  }
}

const Input = createFormsComponent('taro-input', 'input')
const Switch = createFormsComponent('taro-switch', 'change', 'checked')
Vue.component('taro-input', Input)
Vue.component('taro-switch', Switch)

Суммировать

Когда мы хотим создать некоторые компоненты, не зависящие от фреймворка, веб-компоненты будут хорошим выбором. Например, кросс-командная совместная работа, когда технологические стеки двух сторон различаются, но требуются некоторые общие компоненты.

Преобразование веб-компонентов библиотеки компонентов синтаксиса React на этот раз не что иное, как перестройка библиотеки компонентов Vue. Но в будущем, когда Taro будет поддерживать использование других фреймворков для написания мультитерминальных приложений, нужно будет только написать связующий слой для совместимости соответствующего фреймворка с веб-компонентами и трафаретом, в общем, это все еще стоит.

Что касается связующего слоя, в отрасли существует множество решений, совместимых с React, но они совместимы и с веб-компонентами.reactify-wc, с Stencil можно использовать официальный плагинStencil DS Plugin. Если Vue должна быть совместима со Stencil или необходимо повысить гибкость совместимости, рекомендуется вручную написать связующий слой.

Эта статья кратко представляет процесс преобразования Taro Next, веб-компонентов, Stencil и библиотеки компонентов, основанной на Stencil, в надежде помочь читателям и вдохновить их.


Добро пожаловать в блог Bump Labs:aotu.io

Или обратите внимание на официальный аккаунт AOTULabs и время от времени публикуйте статьи:

image