Поскольку разработка небольших программ относительно примитивна, сложна и громоздка, она сильно отличается от наших основных методов разработки, поэтому для повышения эффективности нашей разработки небольших программ на рынке существует множество фреймворков для небольших программ: mpvue, Таро, уни-приложение Подождите, эти фреймворки более или менее уводят нас в современный путь разработки, они позволяют вамРазрабатывайте апплеты с помощью React или Vue.. Сегодня я поделюсь как пользоватьсяVue 3.0 для создания фреймворка для небольших программ.
Базовые знания
Vue 3.0
Просто взгляните на то, что нового в Vue 3.0:
Composition-API
Composition-API — это набор API-интерфейсов, который позволяет легко извлекать логические функции.По сравнению с предыдущим API-интерфейсом Options его возможности организации кода сильнее, одна и та же логика может быть написана в одном и том же месте, а границы между каждой логикой четкие. .
Для иллюстрации см. следующий пример:
<template>
<div>
<div>Add todo list</div>
<div class="field">
<input @input="handleInput" :value="todo" />
</div>
<button @click="handleAdd">Add +</button>
</div>
</template>
<script>
import { ref, reactive } from 'vue';
import { useMainStore } from '@/store';
export default {
setup() {
const todo = ref('');
const state = reactive({
a: 1,
b: 'hello world'
});
const store = useMainStore();
const handleInput = (e) => {
todo.value = e.detail.value;
state.a = 'dsdas';
};
const handleAdd = () => {
store.addTodo(todo.value);
};
return {
handleInput,
todo,
handleAdd,
};
},
};
</script>
Fragment, Teleport
Как и в случае с фрагментом React, мы больше не ограничены потребностью в одном корневом узле при написании шаблонов Vue.В Vue3.0 может быть несколько корневых узлов.
<Fragment>
<component-1 />
<component-2 />
</Fragment>
Teleport использует прямой декларативный способ монтирования дочерних компонентов в другом месте DOM, аналогичный порталу React, но более мощный.
<body>
<div id="app" class="demo">
<h3>Move the #content with the portal component</h3>
<div>
<teleport to="#endofbody">
<p id="content">
This should be moved to #endofbody.
</p>
</teleport>
<span>This content should be nested</span>
</div>
</div>
<div id="endofbody"></div>
</body>
Улучшенная поддержка TypeScript
Теперь код Vue 3.0 написан TS, плюс Composition-Api, вы можете легко переключаться на TS при написании бизнес-кода.
Custom Render API
Используя этот API, вы можете легко создать собственный слой рендеринга,Это то, на чем мы должны сосредоточиться в следующий раз.
import {
createRenderer,
CreateAppFunction,
} from '@vue/runtime-core';
export const { render, createApp: baseCreateApp } = createRenderer({
patchProp, // 修改 props 的函数
...nodeOps, // 修改 dom 节点的函数
});
render();
Апплеты
Для разработки страницы апплета в основном нам нужно всего четыре файла:
index.js
index.js — это место, где мы пишем логику нашего кода.
- Существует функция Page, которая является конфигурацией объекта, аналогично конфигурации опций Vue, есть атрибут данных, в котором хранятся инициализированные данные.
- Если вы хотите изменить данные, чтобы изменить представление, вам нужно вызвать setData, чтобы изменить представление, например, реагировать.
Page({
data: {
text: 'hello word'
},
onLoad() {
this.setData({
text: 'xxxxx'
})
},
onReady() {},
onShow() {},
onHide() {},
onUnload() {},
handleClick() {
this.setData({
text: 'hello word'
})
}
})
index.ttml
В index.ttml мы пишем наши шаблоны представлений.
- Подобно шаблону vue, нам нужно определить шаблон, прежде чем мы сможем отобразить представление.
- Уведомление:Вы не можете изменить DOM определенного шаблона непосредственно в index.js. Вы можете только сначала определить его. Это связано с двухпоточностью архитектуры апплета, которая разделена на логический уровень и уровень рендеринга. Код index.js мы написали запуски в логике.В слое index.ttml запускается в слое рендеринга, и два потока обмениваются данными через setData.
<view>
<view bindtap="handleClick">{{text}}</view>
</view>
index.json
Там, где настраиваются страницы и компоненты апплета, параметры пока не указаны, но этот файл обязательно должен присутствовать.
index.ttss
Как следует из названия, это место для написания стилей, похожее на CSS.
шаблон
Для удобства инкапсуляции апплет может заранее определить шаблон, а затем импортировать шаблон там, где это необходимо, что немного похоже на использование шаблона импорта в ejs и pug.
// 提前定义一个模板,里面用到了 text 这个变量
<template name="view">
<view>{{text}}</view>
</template>
// 使用这个模板,我们把 data 里面定义的变量给传进去,这样模板里面就能渲染出 text 数据。
<template is="view" data="{{text: text}}"/>
Динамический шаблон
Как упоминалось выше, в апплетеУзлы DOM не могут быть динамически изменены, можно только заранее определить шаблон, а потом модифицировать представление в виде setData.
Но у апплета есть более динамичная функция, называемаяДинамический выбор шаблонов.
// 使用这个模板
<template is="{{type}}" data="{{item: item}}"/>
Вышеуказанное является атрибутомтип динамический, это переменная,Различные шаблоны могут быть выбраны в соответствии со значением типа, например, когда типом является представление, будет отображаться шаблон представления, который мы определили заранее.
Пользовательский слой рендеринга (очень важно)
Приближается главное событие, как мы можем его использоватьУдобный настраиваемый слой рендеринга Vue 3.0комбинироватьОсобенности шаблонов динамического выбора для мини-программКак насчет фреймворка для написания небольшой программы?
import {
createRenderer,
CreateAppFunction,
} from '@vue/runtime-core';
export const { render, createApp: baseCreateApp } = createRenderer({
patchProp, // 修改 props 的函数
...nodeOps, // 修改 dom 节点的函数
});
Мы видим, что`createRenderer`Функция принимает два параметра: patchProp и nodeOps.
nodeOps
nodeOps представляет собой некоторые операции, которые модифицируют узел node, чтобы можно было изменить представление, например, в среде браузера Vue 3.0 это написано так:
import { RendererOptions } from '@vue/runtime-core'
export const svgNS = 'http://www.w3.org/2000/svg'
const doc = (typeof document !== 'undefined' ? document : null) as Document
let tempContainer: HTMLElement
let tempSVGContainer: SVGElement
// 浏览器环境下的 nodeOps
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
insert: (child, parent, anchor) => {
parent.insertBefore(child, anchor || null)
},
remove: child => {
const parent = child.parentNode
if (parent) {
parent.removeChild(child)
}
},
createElement: (tag, isSVG, is): Element =>
isSVG
? doc.createElementNS(svgNS, tag)
: doc.createElement(tag, is ? { is } : undefined),
createText: text => doc.createTextNode(text),
createComment: text => doc.createComment(text),
setText: (node, text) => {
node.nodeValue = text
},
setElementText: (el, text) => {
el.textContent = text
},
parentNode: node => node.parentNode as Element | null,
nextSibling: node => node.nextSibling,
querySelector: selector => doc.querySelector(selector),
setScopeId(el, id) {
el.setAttribute(id, '')
},
cloneNode(el) {
return el.cloneNode(true)
},
}
ФактическиVue независимо от того, как меняются данные, чтобы отобразить данные в представленииВызываются некоторые API DOM, такие как doc.createElement и doc.createTextNode выше и т. д.
VNode
Из-за ограничений апплета мы не можем напрямую изменять DOM, как среду браузера.Затем мы можем имитировать среду браузера и создать виртуальный DOM, который мы называем VNode..
class VNode {
id: number;
type: string;
props?: Record<string, any>;
text?: string;
children: VNode[] = [];
eventListeners?: Record<string, Function | Function[]> | null;
parentNode?: VNode | null;
nextSibling?: VNode | null;
constructor({
id,
type,
props = {},
text,
}: {
id: number;
type: string;
props?: Record<string, any>;
text?: string;
}) {
this.type = type;
this.props = props;
this.text = text;
this.id = id;
}
appendChild(newNode: VNode) {
if (this.children.find((child) => child.id === newNode.id)) {
this.removeChild(newNode);
}
newNode.parentNode = this;
this.children.push(newNode);
setState({ node: newNode, data: newNode.toJSON() }); // 调用了小程序的 setData
}
insertBefore(newNode: VNode, anchor: VNode) {
newNode.parentNode = this;
newNode.nextSibling = anchor;
if (this.children.find((child) => child.id === newNode.id)) {
this.removeChild(newNode);
}
const anchorIndex = this.children.indexOf(anchor);
this.children.splice(anchorIndex, 0, newNode);
setState({
node: this,
key: '.children',
data: this.children.map((c) => c.toJSON()),
}); // 调用了小程序的 setData
}
removeChild(child: VNode) {
const index = this.children.findIndex((node) => node.id === child.id);
if (index < 0) {
return;
}
if (index === 0) {
this.children = [];
} else {
this.children[index - 1].nextSibling = this.children[index + 1];
this.children.splice(index, 1);
}
setState({
node: this,
key: '.children',
data: this.children.map((c) => c.toJSON()),
});
}
setText(text: string) {
if (this.type === TYPE.RAWTEXT) {
this.text = text;
setState({ node: this, key: '.text', data: text });
return;
}
if (!this.children.length) {
this.appendChild(
new VNode({
type: TYPE.RAWTEXT,
id: generate(),
text,
})
);
return;
}
this.children[0].text = text;
setState({ node: this, key: '.children[0].text', data: text });
}
path(): string {
if (!this.parentNode) {
return 'root';
}
const path = this.parentNode.path();
return [
...(path === 'root' ? ['root'] : path),
'.children[',
this.parentNode.children.indexOf(this) + ']',
].join('');
}
toJSON(): RawNode {
if (this.type === TYPE.RAWTEXT) {
return {
type: this.type,
text: this.text,
};
}
return {
id: this.id,
type: this.type,
props: this.props,
children: this.children && this.children.map((c) => c.toJSON()),
text: this.text,
};
}
}
Вы можете видеть, что созданный нами VNode похож на DOM, а также есть некоторые методы для манипулирования узлами Node и, наконец, для создания дерева узлов. Мы можем имитировать метод записи nodeOps в среде браузера vue и сначала изменить наш VNode.При изменении узла Node мы можем вызвать метод setData апплета.
// 小程序环境下的 nodeOps,主要是修改 VNode
export const nodeOps = {
insert: (child: VNode, parent: VNode, anchor?: VNode) => {
if (anchor != null) {
parent.insertBefore(child, anchor);
} else {
parent.appendChild(child);
}
},
remove: (child: VNode) => {
const parent = child.parentNode;
if (parent != null) {
parent.removeChild(child);
}
},
createElement: (tag: string): VNode =>
new VNode({ type: tag, id: generate() }),
createText: (text: string): VNode =>
new VNode({ type: TYPE.RAWTEXT, text, id: generate() }),
createComment: (): VNode => new VNode({ type: TYPE.RAWTEXT, id: generate() }),
setText: (node: VNode, text: string) => {
node.setText(text);
},
setElementText: (el: VNode, text: string) => {
el.setText(text);
},
parentNode: (node: VNode): VNode | null => node.parentNode ?? null,
nextSibling: (node: VNode): VNode | null => node.nextSibling ?? null,
querySelector: (): VNode | null => getApp()._root,
setScopeId(el: VNode, id: string) {
if (el.props) {
const className = el.props.class;
el.props.class = className ? className + ' ' + id : id;
}
},
};
toJSON()
Недостаточно просто создать VNode, мы должны отобразить его в апплете.Чтобы сначала отобразить данные в апплете, это должны быть данные, определенные в атрибуте данных заранее, и они могут быть только обычного типа данных.
Page({
data: {
root: {
type: 'view',
props: {
class: 'xxx'
},
children: [...]
}
}
})
Метод toJSON предназначен для форматирования VNode в обычный объект, чтобы апплет мог отображать данные.
Типы интерфейса следующие:
interface RawNode {
id?: number;
type: string; // view,input, button
props?: Record<string, any>;
children?: RawNode[];
text?: string; // 文本
}
Вы знакомы со структурой ВДОМ?
path()
Мы видим, что в определенном нами виртуальном узле есть метод path().Этот метод предназначен для получения пути узла узла во всем дереве узлов, а затем использования пути для изменения определенного узла узла.
const path = Node.path(); // root.children[2].props.class
// 然后我们可以直接这样来更新小程序
this.setData({
'root.children[2].props.class': 'xxxxx'
})
В сочетании с шаблонами динамического выбора
<template name="$_TPL">
<block tt:for="{{root.children}}" tt:key="{{id}}">
<template is="{{'$_' + item.type}}" data="{{item: item}}"/>
</block>
</template>
<template name="$_input">
// input 有三个属性 class 和 bindinput 和 value 对应 vue 文件 template 里的 input 上的属性 class @input value
<input class="{{item.props['class']}}" bindinput="{{item.props['bindinput']}}" value="{{item.props['value']}}">
<block tt:for="{{item.children}}" tt:key="{{id}}">
<template is="{{'$_' + item.type}}" data="{{item}}"/>
</block>
</input>
</template>
<template name="$_button">
// button 有两个属性 class 和 bindTap 对应 vue 文件 template 里的 button 上的属性
<button class="{{item.props['class']}}" bindtap="{{item.props['bindtap']}}">
<block tt:for="{{item.children}}" tt:key="{{id}}">
<template is="{{'$_' + item.type}}" data="{{item}}"/>
</block>
</button>
</template>
<template name="$_view">
<view class="{{item.props['class']}}" bindtap="{{item.props['bindtap']}}">
<block tt:for="{{item.children}}" tt:key="{{id}}">
<template is="{{'$_' + item.type}}" data="{{item}}"/>
</block>
</view>
</template>
<template name="$_rawText">{{item.text}}</template>
слой компиляции
Код, который мы пишем, должен быть кодом Vue, а не приведенным выше кодом шаблона, так как же скомпилировать код Vue в приведенный выше код шаблона?
Сначала взгляните на общую схему архитектуры:
Template
Если бизнес-код, который мы пишем, является распространенным шаблоном шаблона инструкций vue, то мы можем использовать @vue/compile-core внизу, чтобы проанализировать шаблон Vue, а затем просмотреть проанализированный AST для сбора тегов и реквизитов, используемых в нем.
import { parse } from '@vue/compiler-sfc';
import {
baseCompile,
} from '@vue/compiler-core';
const { descriptor } = parse(source, {
filename: this.resourcePath,
});
// 遍历这个 ast 去收集 tag 和 props
const { ast } = baseCompile(descriptor.template.content);
JSX/TSX
Если бизнес-код, который мы пишем, — это JSX/TSX, то мы можем написать плагин для Babel, который собирает теги и реквизиты, проходит через AST в плагине Babel и собирает теги и реквизиты.
Окончательный сгенерированный ttml
Предположим, у нас есть файл .vue:
<template>
<div class="container is-fluid">
<div class="subtitle is-3">Add todo list</div>
<div class="field">
<div class="control">
<input class="input is-info" @input="handleInput" :value="todo" />
</div>
</div>
<button class="button is-primary is-light" @click="handleAdd">Add +</button>
</div>
</template>
<script>
import { ref } from 'vue';
import { useMainStore } from '@/store';
export default {
setup() {
const todo = ref('');
const store = useMainStore();
const handleInput = (e) => {
todo.value = e.detail.value;
};
const handleAdd = () => {
store.addTodo(todo.value);
};
return {
handleInput,
todo,
handleAdd,
};
},
};
</script>
<style>
.container {
text-align: center;
margin-top: 30px;
}
</style>
Будет сгенерирован следующий шаблон:
<template name="$_TPL">
<block tt:for="{{root.children}}" tt:key="{{id}}">
<template is="{{'$_' + item.type}}" data="{{item: item}}"/>
</block>
</template>
<template name="$_input">
// input 有三个属性 class 和 bindinput 和 value 对应 vue 文件 template 里的 input 上的属性 class @input value
<input class="{{item.props['class']}}" bindinput="{{item.props['bindinput']}}" value="{{item.props['value']}}">
<block tt:for="{{item.children}}" tt:key="{{id}}">
<template is="{{'$_' + item.type}}" data="{{item}}"/>
</block>
</input>
</template>
<template name="$_button">
// button 有两个属性 class 和 bindTap 对应 vue 文件 template 里的 button 上的属性
<button class="{{item.props['class']}}" bindtap="{{item.props['bindtap']}}">
<block tt:for="{{item.children}}" tt:key="{{id}}">
<template is="{{'$_' + item.type}}" data="{{item}}"/>
</block>
</button>
</template>
<template name="$_view">
<view class="{{item.props['class']}}" bindtap="{{item.props['bindtap']}}">
<block tt:for="{{item.children}}" tt:key="{{id}}">
<template is="{{'$_' + item.type}}" data="{{item}}"/>
</block>
</view>
</template>
<template name="$_rawText">{{item.text}}</template>
Видно, что, начиная с корневого узла $_TPL, вы можете выбрать каждый шаблон (ввод, кнопка, вид...), сгенерированный ниже, в соответствии с каждым item.type, и в каждом шаблоне есть цикл, так что вы можно комбинировать VNodeбесконечный рекурсивный рендеринг.
Присоединяйтесь к нам
Мы являемся передовой игровой командой байтов, занимающейся издательским бизнесом игр.Мы выпустили множество игр, в основном с помощью технических средств, чтобы обеспечить эффективные мобильные игры, мини-игры и другие игры.Служба распространения игра такжеканал распределения, упрощая разработку игр, делая игровой опыт более экстремальным, а распространение игр более эффективным.
Наш стек технологий охватывает широкий спектр, от ПК, H5, RN до небольших программ. Благодаря сильной технической атмосфере и тесным техническим обменам внутри кафедры каждый студент имеет возможность участвовать в создании базовой технологии. И есть углубленные исследования в направлении внешней визуализации и инженерного строительства, а также углубленные исследования в направлении большой оптимизации производительности переднего плана для созданияВысокопроизводительные интерфейсные приложениякак основная цель. Также вАпплетыВ стеке технологий много накоплений, и всестороннее исследованиеОптимизация производительности, собственная разработка фреймворкаИОткрытый исходный кодплан и видение.
Электронная почта для доставки резюме:tech@bytedance.com,заголовок письма:Название - Годы работы - Front End Game.
Добро пожаловать в "байтовый интерфейс"
Контактный адрес электронной почты для доставки резюме "tech@bytedance.com"