некоторые понятия
Vue Composition API (VCA) на самом деле просто более явно раскрывает отзывчивую систему самого Vue.
Это не функционально, просто API представлен как функция.
3.0 Производительность, скомпилированная по шаблону, будет в несколько раз быстрее, чем написанная вручную jsx.
- Ю Юси
Традиционные данные Vue2, вычисляемые, наблюдаемые, записываются методы, мы называем это «API опций (API опций)»
Vue3 использует Composition API (VCA) для организации кода в соответствии с логическими функциями, и API, связанный с функциями, будет собран вместе.
Логическое повторное использование Vue и React
слишком далеко,
Vue: Mixins (примеси), HOC (компоненты более высокого порядка), слоты с заданной областью действия, Vue Composition API (VCA/Composition API).
React: Mixins, HOC, Render Props, Hooks.
Мы видим, что это история все лучшего и лучшего роста.Я не буду повторять здесь примеры.В центре внимания этой статьи находится VCA, который больше склоняется к понятию «комбинация».
Vue3 в 5 измерениях
1. Каркас
Пример для понимания VCA в первую очередь
В Vue концепция абстрактных компонентов инкапсуляции решает проблему, состоящую в том, что чем больше модулей на странице, тем больше она раздувается. Но даже при инкапсуляции компонентов по мере роста приложения вы будете находить на странице все больше и больше логических функциональных точек.
data/computed/watch/methodsЛогическая функция будет постоянно вставляться в СВУ, поэтому логика должна быть извлечена, объединена и использована повторно.Это и есть СВУ.
Возьмем простой пример:
Мы хотим реализовать 3 логики
- Согласно полученным данным форма id
- Данные поиска и фильтра
- Всплывающее окно для добавления данных в таблицу
Обработка параметров Vue2 API
Для удобства чтения часть кода опущена, но это не влияет на наше понимание СВУ.
// 逻辑功能(1)
const getTableDataApi = id => {
const mockData = {
1: [
{ id: 11, name: '张三1' },
{ id: 12, name: '李四1' },
{ id: 13, name: '王五1' }
],
2: [
{ id: 21, name: '张三2' },
{ id: 22, name: '李四2' },
{ id: 23, name: '王五2' }
]
};
return new Promise(resolve => {
setTimeout(() => {
resolve(mockData[id] || []);
}, 1000);
});
};
export default {
name: 'VCADemo',
components: { Modal },
data() {
return {
// 逻辑功能(1)
id: 1,
table: [],
// 逻辑功能(2)
search: '',
// 逻辑功能(3)
modalShow: false,
form: {
id: '',
name: ''
}
};
},
computed: {
// 逻辑功能(2)
getTableDataBySearch() {
return this.table.filter(item => item.name.indexOf(this.search) !== -1);
}
},
watch: {
// 逻辑功能(1)
id: 'getTableData'
},
mounted() {
// 逻辑功能(1)
this.getTableData();
},
methods: {
// 逻辑功能(1)
async getTableData() {
const res = await getTableDataApi(this.id);
this.table = res;
},
// 逻辑功能(3)
handleAdd() {
this.modalShow = true;
},
// 逻辑功能(3)
handlePost() {
const { id, name } = this.form;
this.table.push({ id, name });
this.modalShow = false;
}
}
};
Вот просто пример простой логики. Если проект сложный, логика увеличивается. Включая логическое изменение, нам может потребоваться изменить одни и те же функциональные точки, распределенные в разных местах, что увеличивает стоимость обслуживания.
Обработка API композиции Vue3
Давайте сосредоточимся на логике, извлечем логику, сначала посмотрим на структуру кода основного тела.
import useTable from './composables/useTable';
import useSearch from './composables/useSearch';
import useAdd from './composables/useAdd';
export default defineComponent({
name: 'VCADemo',
components: { Modal },
setup() {
// 逻辑功能(1)
const { id, table, getTable } = useTable(id);
// 逻辑功能(2)
const { search, getTableBySearch } = useSearch(table);
// 逻辑功能(3)
const { modalShow, form, handleAdd, handlePost } = useAdd(table);
return {
id,
table,
getTable,
search,
getTableBySearch,
modalShow,
form,
handleAdd,
handlePost
};
}
});
Программа установки получает два параметра: ProOPS, Context. Вы можете вернуть объект, и свойства объектаproxyДа, будет выполняться отслеживание монитора и адаптивный рендеринг шаблона.
Давайте сосредоточимся на одной из логик,useTable, в общем будем использоватьuseНазовите его в начале, на вкус он такой~
// VCADemo/composables/useTable.ts
// 逻辑功能(1)相关
import { ref, onMounted, watch, Ref } from 'vue';
import { ITable } from '../index.type';
const getTableApi = (id: number): Promise<ITable[]> => {
const mockData: { [key: number]: ITable[] } = {
1: [
{ id: '11', name: '张三1' },
{ id: '12', name: '李四1' },
{ id: '13', name: '王五1' }
],
2: [
{ id: '21', name: '张三2' },
{ id: '22', name: '李四2' },
{ id: '23', name: '王五2' }
]
};
return new Promise(resolve => {
setTimeout(() => {
resolve(mockData[id] || []);
}, 1000);
});
};
export default function useTable() {
const id = ref<number>(1);
const table = ref<ITable[]>([]);
const getTable = async () => {
table.value = await getTableApi(id.value);
};
onMounted(getTable);
watch(id, getTable);
return {
id,
table,
getTable
};
}
Мы разделили соответствующую логику независимо друг от друга и "объединили" их вместе. Мы видим, что в пакете vue представлено много независимых функций, которые мы можем использовать. Это больше не объектно-ориентированный подход и пахнет FP~
Приведенный выше пример впервые иллюстрирует преимущества VCA. Ядром Vue3, конечно же, является VCA. Vue3 — это не просто VCA. Давайте посмотрим с любопытством~
Жизненный цикл, Vue2 против Vue3
| API опций (Vue2) | Крюк внутри установки (Vue3) |
|---|---|
| beforeCreate | Not needed* |
| created | Not needed* |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |
| renderTracked | onRenderTracked |
| renderTriggered | onRenderTriggered |
Крюк внутри установки, как следует из названия, рекомендует VCAsetupЭтот большой метод записывает различные точки нашей логической функции.
Компоненты телепорта
Телепортация, которая монтирует DOM-элемент компонента на любой указанный DOM-элемент, согласуется с концепцией порталов React.
Типичный пример, мы вызываем модальное всплывающее окно в компоненте.Всплывающее окно, которое мы хотим, должно быть таким, абсолютно по центру и на самом высоком уровне, например:
Структура компонента такая
<Home>
<Modal />
</Home>
Но если в родительском компоненте Home есть такой стиль, напримерtransform:
повлияет на позицию Modal, даже если Modal используетсяposition:fixedнайти, например:
Вот почему нам нужно использовать компонент Teleport, чтобы помочь нам «выпрыгнуть» из контейнера, избежать контроля некоторыми ограничениями родительского компонента и смонтировать элементы DOM компонента под телом, например:
<Teleport to="body">
<div v-if="show">
...Modal 组件的 DOM 结构...
</div>
</Teleport>
Примечание. Даже если Modal выпрыгивает из контейнера, «отношения между родительским и дочерним компонентами» сохраняются, но положение элемента DOM перемещается.
Асинхронный компонент (defineAsyncComponent)
Мы все знаем, что в Vue2 также есть концепция асинхронных компонентов, но в целом она неполна~, Vue3 предоставляетdefineAsyncComponentметод сSuspenseВстроенные компоненты, мы можем использовать их для создания элегантной схемы загрузки асинхронных компонентов.
Посмотрите прямо на код:
HOCLazy/index.tsx
import { defineAsyncComponent, defineComponent } from 'vue';
import MySuspense from './MySuspense.vue';
export default function HOCLazy(chunk: any, isComponent: boolean = false) {
const wrappedComponent = defineAsyncComponent(chunk);
return defineComponent({
name: 'HOCLazy',
setup() {
const props = { isComponent, wrappedComponent };
return () => <MySuspense {...props} />;
}
});
}
Объяснение: HOCLazy получает два параметра,chunkМы часто используем асинхронный метод загрузки компонентов, например:chunk=()=>import(xxx.vue),isComponentУказывает, что текущий «компонент» находится на уровне компонента или на уровне страницы, судя поisComponentчтобы соответствовать различным операциям «загрузки».
HOCLazy/MySuspense.vue
<template>
<Suspense>
<template #default>
<component :is="wrappedComponent"
v-bind="$attrs" />
</template>
<template #fallback>
<div>
<Teleport to="body"
:disabled="isComponent">
<div v-if="delayShow"
class="loading"
:class="{component:isComponent}">
<!-- 组件和页面有两种不一样的loading方式,这里不再详细封装 -->
<div> {{isComponent?'组件级':'页面级'}}Loading ...</div>
</div>
</Teleport>
</div>
</template>
</Suspense>
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent, ref, onMounted } from 'vue';
export default defineComponent({
name: 'HOCLazy',
props: ['isComponent', 'wrappedComponent'],
setup(props) {
const delayShow = ref<boolean>(false);
onMounted(() => {
setTimeout(() => {
delayShow.value = true;
// delay 自己拿捏,也可以以 props 的方式传入
}, 300);
});
return { ...props, delayShow };
}
});
</script>
<style lang="less" scoped>
.loading {
// 组件级样式
&.component {
}
// 页面级样式
}
</style>
объяснять:
- Компоненты приостановки имеют два слота, именованные слоты.
fallbackМы можем понять это здесь как заполнитель загрузки, резервное содержимое перед асинхронным компонентом не отображалось. - Динамический компонент Vue также используется здесь для гибкой передачи асинхронного компонента.
v-bind="$attrs"Чтобы реквизиты, которые мы передаем целевому компоненту, не исчезали. - В качестве запасного варианта мы используем суждение isComponent для отображения различных загрузок, потому что мы хотим, чтобы загрузка на уровне страницы была «глобальной», а уровень компонента — в исходном потоке документов, который используется здесь.
Teleport :disabled="isComponent"чтобы контролировать, выпрыгивать ли. - Внимательные друзья обнаружат, что тут сделан отложенный показ
delayShow, если у нас нет этой задержки, в случае хорошего сетевого окружения загрузка будет мигать каждый раз, будет ощущение "антиоптимизации".
Позвоните в HOCLazy:
Чтобы лучше увидеть эффект, мы инкапсулируем метод slow для задержки загрузки компонента:
utils/slow.ts
const slow = (comp: any, delay: number = 1000): Promise<any> => {
return new Promise(resolve => {
setTimeout(() => resolve(comp), delay);
});
};
export default slow;
вызов (уровень компонента)
<template>
<LazyComp1 str="hello~" />
</template>
const LazyComp1 = HOCLazy(
() => slow(import('@/components/LazyComp1.vue'), 1000),
true
);
// ...
components: {
LazyComp1
},
// ...
Взгляните на эффект:
По сути, это то же самое, что и в React
React.lazy + React.SuspenseКонцепция та же, статья написана раньше«Отложенная загрузка версии React丨User Experience丨Hook», вы можете посмотреть и сделать сравнение ~
Разница между ref, reactive, toRef, toRefs
исх (ссылка)
Существование ref и reactive заключается в отслеживании изменений значений (отзывчивости). ref имеет концепцию "обертывания", которая используется для переноса примитивных типов значений, таких как строка и число. Мы все знаем, что это не ссылочный тип, который не может отслеживать последующие изменения. ref возвращает содержащий.valueсвойства объекта.
setup(props, context) {
const count = ref<number>(1);
// 赋值
count.value = 2;
// 读取
console.log('count.value :>> ', count.value);
return { count };
}
В шаблоне объект-обертка ref будет автоматически развернут (Ref Unwrapping), то есть нам не нужно его использовать в шаблоне..value
<template>
{{count}}
</template>
reactive
В Vue2.Vue.observable()является концепцией.
Используется для возврата реактивного объекта, например:
const obj = reactive({
count: 0
})
// 改变
obj.count++
Примечание: он используется для возврата реактивного объекта, который сам является объектом, поэтому не требуется обертывания. Используем его свойства, добавлять не нужно.valueчтобы получить.
toRefs
Официальный веб-сайт: поскольку свойства являются реактивными, вы не можете использовать деструктурирование ES6, потому что оно устраняет отзывчивость свойств.
давайте следоватьsetupСвязанные операции реквизитов метода:
<template>
{{name}}
<button @click="handleClick">点我</button>
</template>
// ...
props: {
name: { type: String, default: ' ' }
},
setup(props) {
const { name } = props;
const handleClick = () => {
console.log('name :>> ', name);
};
return { handleClick };
}
// ...
Примечание: реквизиты не нужно возвращать через функцию настройки, а соответствующее значение также можно привязать в шаблоне.
Мы все знаем, что деструктурирование — это удобный способ компиляции es6 в es5, например:
// es6 syntax
const { name } = props;
// to es5 syntax
var name = props.name;
Предполагая, что родительский компонент изменяет значение props.name, когда мы снова нажимаем кнопку, выходное имя по-прежнему будет прежним значением и не изменится.На самом деле это базовый пункт знаний js.
Для того, чтобы облегчить нашу упаковку,toRefsПод пакетной упаковкой можно понимать объекты реквизита, такие как:
const { name } = toRefs(props);
const handleClick = () => {
// 因为是包装对象,所以读取的时候要用.value
console.log('name :>> ', name.value);
};
Все понятно, потому что мы собираемся использовать деконструкцию,toRefsрешение принято.
toRef
Использование Toref - это дополнительный параметр, который позволяет нам обернуть ключ, например:
const name = toRef(props,'name');
console.log('name :>> ', name.value);
watchEffect vs watch
Метод watch в Vue3 похож на концепцию Vue2, и watchEffect немного запутает нас. По сути, watchEffect примерно похож на watch, разница в следующем:
что часы могут сделать
- Ленивые побочные эффекты
- Уточните, какое состояние должно инициировать повторный запуск слушателя.
- Доступ к значениям до и после прослушивания изменений состояния
Для метода часов Vue2 «Часы» VUE3 имеют концепцию «очистки побочных эффектов», и мы ориентируемся на это.
возьми это здесьwatchEffectНапример:
watchEffect: он немедленно выполняет переданную функцию, реактивно отслеживая ее зависимости и повторно запуская функцию при изменении ее зависимостей.
Простая структура метода watchEffect
watchEffect(onInvalidate => {
// 执行副作用
// do something...
onInvalidate(() => {
// 执行/清理失效回调
// do something...
})
})
Выполните обратный вызов аннулирования, есть две возможности
- Когда побочный эффект вот-вот повторится, то есть когда отслеживаемые данные изменятся
- Когда компонент удален
Пример: мы хотим инициировать запрос, чтобы получить информацию о «фруктах» через id. Мы отслеживаем id. Когда id переключается слишком часто (не дождались успешного возврата последних асинхронных данных). может привести к финалуid=1данные обложкиid=2данные, на которые мы не рассчитывали.
Давайте смоделируем и решим этот сценарий:
Мок-интерфейс getFruitsById
interface IFruit {
id: number;
name: string;
imgs: string;
}
const list: { [key: number]: IFruit } = {
1: { id: 1, name: '苹果', imgs: 'https://xxx.apple.jpg' },
2: { id: 2, name: '香蕉', imgs: 'https://xxx.banana.jpg' }
};
const getFruitsById = (
id: number,
delay: number = 3000
): [Promise<IFruit>, () => void] => {
let _reject: (reason?: any) => void;
const _promise: Promise<IFruit> = new Promise((resolve, reject) => {
_reject = reject;
setTimeout(() => {
resolve(list[id]);
}, delay);
});
return [
_promise,
() =>
_reject({
message: 'abort~'
})
];
};
Это инкапсулирует метод «отменить запрос» и использует отклонение для завершения этого действия.
в методе установки
setup() {
const id = ref<number>(1);
const detail = ref<IFruit | {}>({});
watchEffect(async onInvalidate => {
onInvalidate(() => {
cancel && cancel();
});
// 模拟id=2的时候请求时间 1s,id=1的时候请求时间 2s
const [p, cancel] = getFruitsById(id.value, id.value === 2 ? 1000 : 2000);
const res = await p;
detail.value = res;
});
// 模拟频繁切换id,获取香蕉的时候,获取苹果的结果还没有回来,取消苹果的请求,保证数据不会被覆盖
id.value = 2;
// 最后 detail 值为 { "id": 2, "name": "香蕉", "imgs": "https://xxx.banana.jpg" }
}
если не выполненоcancel(), то данные детализации будут{ "id": 1, "name": "苹果", "imgs": "https://xxx.apple.jpg" }, потому что данные с id=1 относительно "получены с опозданием".
Это распространенный пример в асинхронных сценариях очистки недопустимых обратных вызовов, чтобы гарантировать, что текущие побочные эффекты действительны и не будут перезаписаны. Заинтересованные друзья могут продолжить обучение.
фрагмент
Все мы знаем, что при инкапсуляции компонентов может быть только один корень. Vue3 позволяет нам иметь несколько корней, то есть фрагментов, но есть несколько операций, которые заслуживают нашего внимания.
когдаinheritAttrs=true[默认], компонент автоматически унаследует объединенный класс в корне, например:
Подсборка
<template>
<div class="fragment">
<div>div1</div>
<div>div2</div>
</div>
</template>
Вызывается родительским компонентом, добавлен новый класс
<MyFragment class="extend-class" />
дочерние компоненты будут отображаться как
<div class="fragment extend-class">
<div> div1 </div>
<div> div2 </div>
</div>
Если мы используем фрагменты, нам нужно явно указать атрибуты привязки, такие как дочерние компоненты:
<template>
<div v-bind="$attrs">div1</div>
<div>div2</div>
</template>
emits
В Vue2 мы проверим данные в реквизитах, указав тип, значение по умолчанию, ненулевое значение и т. д. Можно понять, что emit делает то же самое и стандартизирует эммит, например:
// 也可以直接用数组,不做验证
// emits: ['on-update', 'on-other'],
emits: {
// 赋值 null 不验证
'on-other': null,
// 验证
'on-update'(val: number) {
if (val === 1) {
return true;
}
// 自定义报错
console.error('val must be 1');
return false;
}
},
setup(props, ctx) {
const handleEmitUpdate = () => {
// 验证 val 不为 1,控制台报错
ctx.emit('on-update', 2);
};
const handleEmitOther = () => {
ctx.emit('on-other');
};
return { handleEmitUpdate, handleEmitOther };
}
В настройке выпуска больше не используетсяthis.$emit, но второй параметр настройкиcontextконтекст для получения emit .
v-model
Лично мне нравится обновление v-model, которое может улучшить процесс упаковки компонентов~
Предположим, в Vue2 мне нужно инкапсулировать модальный компонент кадра маркера с помощью
showПеременные используются для управления отображением и скрытием всплывающего окна Это значение, безусловно, должно поддерживаться родительским и дочерним компонентами. Из-за одностороннего потока данных вам необходимо генерировать событие в модальном компоненте, а родительский компонент прослушивает событие, чтобы получить и изменить его.showстоимость.
Для удобства у нас будет некоторый синтаксический сахар, например v-model, но на компоненте в Vue2 может быть только одна v-модель, потому что за синтаксическим сахаром стоитvalueа также@inputЕсли есть несколько таких «двусторонних данных модификации», нам нужно использовать синтаксический сахар.syncМодификатор синхронизации.
Vue3 объединяет эти два синтаксических сахара, поэтому теперь мы можем использовать несколько синтаксических сахаров v-модели для компонента, например:
Сначала посмотрите на родительский компонент
<VModel v-model="show"
v-model:model1="check"
v-model:model2.hello="textVal" />
привет это пользовательский модификатор
Мы используем 3 грамматических сахара V-Model на один компонент соответственно.
| синтаксический сахар v-модели | соответствующая опора | соответствующее событие | Свойство, соответствующее пользовательскому модификатору |
|---|---|---|---|
| v-модель (по умолчанию) | modelValue | update:modelValue | без |
| v-model:model1 | model1 | update:model1 | без |
| v-model:model2 | model2 | update:model2 | model2Modifiers |
Таким образом, мы можем более четко инкапсулировать то, что нам нужно сделать в подкомпоненте, например:
VModel.vue
// ...
props: {
modelValue: { type: Boolean, default: false },
model1: { type: Boolean, default: false },
model2: { type: String, default: '' },
model2Modifiers: {
type: Object,
default: () => ({})
}
},
emits: ['update:modelValue', 'update:model1', 'update:model2'],
// ...
key attribute
<template>
<input type="text"
placeholder="请输入账号"
v-if="show" />
<input type="text"
placeholder="请输入邮箱"
v-else />
<button @click="show=!show">Toggle</button>
</template>
V-if/v-else, как это, в Vue2 будет отображать элементы максимально эффективно, обычно повторно используя существующие элементы вместо рендеринга с нуля, поэтому, когда мы вводим первый ввод, а затем переключаем первые два Вход . Значение первого ввода будет сохранено для повторного использования.
В некоторых сценариях мы не используем их повторно, нам нужно добавить уникальный ключ, например:
<template>
<input type="text"
placeholder="请输入账号"
v-if="show"
key="account" />
<input type="text"
placeholder="请输入邮箱"
v-else
key="email" />
<button @click="show=!show">Toggle</button>
</template>
Но в Vue3 нам не нужно явно добавлять ключ, эти два элемента ввода полностью независимы, потому что Vue3 автоматически сгенерирует уникальный ключ для v-if/v-else.
Глобальный API
В VUE2 мы можем выглядеть так для некоторой глобальной конфигурации, например, мы используем плагин
Vue.use({
/* ... */
});
const app1 = new Vue({ el: '#app-1' });
const app2 = new Vue({ el: '#app-2' });
Но таким образом, это повлияет на как корневые экземпляры, то есть он станет неконтролируемым.
Представляем новый API в Vue3createAppметод, который возвращает экземпляр:
import { createApp } from 'vue';
const app = createApp({ /* ... */ });
Затем мы можем смонтировать глобальные связанные методы в этом экземпляре и действовать только в текущем экземпляре, например:
app
.component(/* ... */)
.directive(/* ... */ )
.mixin(/* ... */ )
.use(/* ... */ )
.mount('#app');
Следует отметить, что во Vue2 мы использовалиVue.prototype.$http=()=>{}Этот способ написания заключается в монтировании прототипа «корневого Vue», чтобы мы могли найти его в подкомпоненте через цепочку прототипов.$httpметод, то естьthis.$http.
А в Vue3 нам нужно использовать новое свойство для таких монтированийglobalProperties:
app.config.globalProperties.$http = () => {}
используется внутри установки$http:
setup() {
const {
appContext: {
config: {
globalProperties: { $http }
}
}
} = getCurrentInstance()
}
2. Низкоуровневая оптимизация
Прокси
Основной принцип отзывчивости Vue2 — передатьObject.defineProperty, но у этого подхода есть недостатки. Заставляет Vue взломать некоторые средства, такие как:
- Vue.$set() динамически добавляет новые адаптивные свойства
- Не имея возможности отслеживать изменения массива, нижнему слою Vue необходимо переупаковать некоторые методы работы с массивом. Такие как
push,popи другие методы.
В Vue3 для обработки предпочтительно используется прокси, который представляет весь объект, а не свойства объекта, и может работать со всем объектом. Он не только улучшает производительность, но и не имеет недостатков, упомянутых выше.
Всего два примера:
- Динамически добавлять реактивные свойства
const targetObj = { id: '1', name: 'zhagnsan' };
const proxyObj = new Proxy(targetObj, {
get: function (target, propKey, receiver) {
console.log(`getting key:${propKey}`);
return Reflect.get(...arguments);
},
set: function (target, propKey, value, receiver) {
console.log(`setting key:${propKey},value:${value}`);
return Reflect.set(...arguments);
}
});
proxyObj.age = 18;
// setting key:age,value:18
Как указано выше, используйтеProxyМыproxyObjСвойства, добавленные динамически объектом, также будут перехвачены.
ReflectОбъекты — это новый API ES6 для управления объектами. Он имеет несколько встроенных методов, таких как вышеget / set, здесь можно понять, что мы используемReflectУдобнее, иначе нам нужно что-то вроде:
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return target[propKey];
},
- Перехват операций над массивами
const targetArr = [1, 2];
const proxyArr = new Proxy(targetArr, {
set: function (target, propKey, value, receiver) {
console.log(`setting key:${propKey},value:${value}`);
return Reflect.set(...arguments);
}
});
proxyArr.push('3');
// setting key:2,value:3
// setting key:length,value:3
подъемник статический (hoistStatic) вдом
Мы все знаем, что Vue имеет концепцию виртуального дома, который может эффективно отображать страницы при изменении данных.
Vue3 оптимизирует производительность обновления vdom, простой пример
Template
<div class="div">
<div>content</div>
<div>{{message}}</div>
</div>
После компилятора нет статического продвижения
function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", { class: "div" }, [
_createVNode("div", null, "content"),
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
]))
}
После компилятора идет статическое продвижение
const _hoisted_1 = { class: "div" }
const _hoisted_2 = /*#__PURE__*/_createVNode("div", null, "content", -1 /* HOISTED */)
function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", _hoisted_1, [
_hoisted_2,
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
]))
}
Статическое продвижение включает в себя продвижение «статических узлов» и «статических атрибутов», то есть мы кэшируем некоторые статические узлы, которые не изменятся в переменных, чтобы обеспечить следующий прямой вызов повторного рендеринга.
Если это действие не выполнено, когдаrenderПри повторном выполнении, даже если тег статичен, он будет создан заново, что влечет за собой снижение производительности.
3. С ТС
Основная цель разработки версии 3.0 — улучшить поддержку TypeScript. Первоначально мы рассчитывали достичь этой цели с помощью Class API, но после обсуждения и разработки прототипа мы пришли к выводу, что Class не является правильным способом решения этой проблемы, а API на основе классов по-прежнему имеет проблемы с типами. - Ю Юси
Функциональные API идеально интегрированы с TS.
defineComponent
В TS нам нужно использовать метод DefinedComponent, предоставляемый Vue, который существует исключительно для вывода типа.
вывод реквизита
import { defineComponent } from 'vue';
export default defineComponent({
props: {
val1: String,
val2: { type: String, default: '' },
},
setup(props, context) {
props.val1;
}
})
Когда мы получаем доступ к свойствам в методе установки, мы можем видеть выведенный тип,
- val1 мы не устанавливаем значение по умолчанию, поэтому
string | undefined - И значение val2 имеет значение, поэтому оно
string, как показано на рисунке:
PropType
Давайте обратим внимание на тип, определяемый props.Если это сложный объект, мы будем использовать PropType, чтобы сделать строгое объявление, например:
interface IObj {
id: number;
name: string;
}
obj: {
type: Object as PropType<IObj>,
default: (): IObj => ({ id: 1, name: '张三' })
},
или тип союза
type: {
type: String as PropType<'success' | 'error' | 'warning'>,
default: 'warning'
},
4. build丨Better Tree-Shock (оптимизация Tree-Shaking)
Tree-shark — это устранение бесполезного кода в программе после того, как инструмент сборки собран, чтобы уменьшить размер пакета.
Функциональный API Каждая функция может использоватьimport { method1,method2 } from "xxx";, что очень удобно для древовидной акулы, и имена функций и имена переменных могут быть сжаты, а объекты — нет. Например, мы инкапсулируем инструмент, инструмент предоставляет два метода, сmethod1,method2Вместо.
Мы инкапсулируем их как объект и раскрываем, например:
// utils
const obj = {
method1() {},
method2() {}
};
export default obj;
// 调用
import util from '@/utils';
util.method1();
После упаковки и сжатия webpack это:
a={method1:function(){},method2:function(){}};a.method1();
Посмотрим на это не в виде объекта, а в виде функции:
// utils
export function method1() {}
export function method2() {}
// 调用
import { method1 } from '@/utils';
method1();
После упаковки и сжатия webpack это:
function a(){}a();
На этом примере мы можем понять, почему Vue3 лучше справляется с акулированием деревьев, потому что он использует API на основе функций, например:
import {
defineComponent,
reactive,
ref,
watchEffect,
watch,
onMounted,
toRefs,
toRef
} from 'vue';
5. Компромисс между вариантами API и составом API
Приведенный выше код реализован внутри установки, но Vue3 по-прежнему сохраняет метод записи API опций Vue2, то есть он может «сосуществовать», например:
// ...
setup() {
const val = ref<string>('');
const fn = () => {};
return {
val,
fn
};
},
mounted() {
// 在 mounted 生命周期可以访问到 setup return 出来的对象
console.log(this.val);
this.fn();
},
// ...
В сочетании с реакцией мы знаем, что «функциональные» хуки — это тренд будущего.
Поэтому моя личная рекомендация - использоватьsetupСпособ внутреннего написания логики, потому что Vue3 может полностью предоставить все возможности Vue2.
Суммировать
Лично, будь то React Hook или VCA Vue3, мы можем видеть текущую тенденцию внешнего интерфейса, «более функциональную», что делает повторное использование логики более гибким. Режим ловушки добавляет уровень абстракции React/Vue, «уровень компонента + уровень функции», что позволяет нам обрабатывать логику более подробно и удобно в сопровождении.
Vue3 One Piece, здорово!
Наконец, я желаю вам счастливого Рождества от эссенции фронтенда 🎄~ (я слышал, что паблик-аккаунт с радостью обратит внимание на "эссенцию фронтенда"~