предисловие
во Вьюslotа такжеslot-scopeЭто всегда была продвинутая концепция, которую нечасто затрагивают в нашей повседневной разработке компонентов, но она очень мощная и гибкая.
в версии 2.6
-
slotа такжеslot-scopeОн унифицирован и интегрирован внутри компонента в函数 - Их области рендеринга
子组件 - и может пройти
this.$scopedSlotsпосетить
Это делает опыт разработки этой модели более единства, эта статья основана на2.6.11Последний код для анализа того, как это работает.
Чтобы узнать о синтаксисе слота, обновленном в версии 2.6, если вы мало о нем знаете, вы можете взглянуть на это замечательное официальное объявление.
В качестве простого примера у сообщества есть библиотека для управления асинхронными процессами:vue-promised, используется так:
<Promised :promise="usersPromise">
<template v-slot:pending>
<p>Loading...</p>
</template>
<template v-slot="data">
<ul>
<li v-for="user in data">{{ user.name }}</li>
</ul>
</template>
<template v-slot:rejected="error">
<p>Error: {{ error.message }}</p>
</template>
</Promised>
Как видите, нам нужно всего лишь поставить асинхронность для обработки запроса.promiseПередайте его к компоненту, он автоматически сделает это для насpromise, и в ответ бросаетpending,rejected, а данные после успешного асинхронного выполненияdata.
Это может значительно упростить наш опыт асинхронной разработки, где нам пришлось бы делать это вручную.promise, ручное управление ошибками обработки состояния и т. д.
И все эти мощные функции получают выгоду от Vue предоставленнойslot-scopeФункция, это очень немного близко к гибкости пакетаHook, компонент может быть даже совершенно безразличнымUIРендеринг только помогает родительскому компоненту управлять некоторыми状态.
Аналогия
если у тебя естьReactопыт разработки, собственно, эта аналогияReactсерединаrenderPropsПросто понять. (если у вас нетReactОпыт разработки, пожалуйста, пропустите)
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
// 这是一个对外提供鼠标位置的 render props 组件
class Mouse extends React.Component {
state = { x: 0, y: 0 }
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
// 这里把 children 当做函数执行,来对外提供子组件内部的 state
{this.props.children(this.state)}
</div>
)
}
}
class App extends React.Component {
render() {
return (
<div style={{ height: '100%' }}>
// 这里就很像 Vue 的 作用域插槽
<Mouse>
({ x, y }) => (
// render prop 给了我们所需要的 state 来渲染我们想要的
<h1>The mouse position is ({x}, {y})</h1>
)
</Mouse>
</div>
)
}
})
ReactDOM.render(<App/>, document.getElementById('app'))
Принципиальный анализ
инициализация
Для такого примера
<test>
<template v-slot:bar>
<span>Hello</span>
</template>
<template v-slot:foo="prop">
<span>{{prop.msg}}</span>
</template>
</test>
Этот шаблон будет скомпилирован следующим образом:
with (this) {
return _c("test", {
scopedSlots: _u([
{
key: "bar",
fn: function () {
return [_c("span", [_v("Hello")])];
},
},
{
key: "foo",
fn: function (prop) {
return [_c("span", [_v(_s(prop.msg))])];
},
},
]),
});
}
Затем после серии обработок при инициализации (resolveScopedSlots, normalizeScopedSlots)testэкземпляр компонентаthis.$scopedSlotsвы можете получить доступ к этим двумfoo,barфункция. (Если безымянный,keyбудетdefault. )
ВойтиtestВнутри компонента предполагают, что это определено в этом:
<div>
<slot name="bar"></slot>
<slot name="foo" v-bind="{ msg }"></slot>
</div>
<script>
new Vue({
name: "test",
data() {
return {
msg: "World",
};
},
mounted() {
// 一秒后更新
setTimeout(() => {
this.msg = "Changed";
}, 1000);
},
});
</script>
Такtemplateбудет скомпилирован в такую функцию:
with (this) {
return _c("div", [_t("bar"), _t("foo", null, null, { msg })], 2);
}
Уже есть некоторые подсказки, давайте изучим их дальше_tРеализация функции может быть близка к истине.
_tто естьrenderSlotпсевдоним, упрощенная реализация выглядит так:
export function renderSlot (
name: string,
fallback: ?Array<VNode>,
props: ?Object,
bindObject: ?Object
): ?Array<VNode> {
// 通过 name 拿到函数
const scopedSlotFn = this.$scopedSlots[name]
let nodes
if (scopedSlotFn) { // scoped slot
props = props || {}
// 执行函数返回 vnode
nodes = scopedSlotFn(props) || fallback
}
return nodes
}
на самом деле это очень легко,
если普通插槽, просто вызовите функцию напрямую, чтобы сгенерироватьvnode,если作用域插槽,
непосредственно сpropsто есть{ msg }вызвать функцию для генерацииvnode. После версии 2.6 сокеты, унифицированные как функции, значительно уменьшили умственную нагрузку.
возобновить
надtestкомпонент, через 1с мы передаемthis.msg = "Changed";Запустите адаптивное обновление, после чего скомпилированныйrenderфункция:
with (this) {
return _c("div", [_t("bar"), _t("foo", null, null, { msg })], 2);
}
повторить, на этот разmsgуже обновленоChangedИ, естественно, обновление будет реализовано.
Особый случай — это когда реактивное свойство также используется и обновляется в области действия родительского компонента, например:
<test>
<template v-slot:bar>
<span>Hello {{msgInParent}}</span>
</template>
<template v-slot:foo="prop">
<span>{{prop.msg}}</span>
</template>
</test>
<script>
new Vue({
name: "App",
el: "#app",
mounted() {
setTimeout(() => {
this.msgInParent = "Changed";
}, 1000);
},
data() {
return {
msgInParent: "msgInParent",
};
},
components: {
test: {
name: "test",
data() {
return {
msg: "World",
};
},
template: `
<div>
<slot name="bar"></slot>
<slot name="foo" v-bind="{ msg }"></slot>
</div>
`,
},
},
});
</script>
На самом деле, поскольку реализация_tглобальный контекст рендеринга компонента子组件, то естественно собирается зависимая коллекция子组件зависит от. так вmsgInParentПосле обновления он фактически запускает повторный рендеринг подкомпонентов напрямую, по сравнению с версией 2.5 это оптимизация.
Тогда есть некоторые дополнительные ситуации, такие какtemplateТамv-if,v-forВ этом случае, например:
<test>
<template v-slot:bar v-if="show">
<span>Hello</span>
</template>
</test>
function render() {
with(this) {
return _c('test', {
scopedSlots: _u([(show) ? {
key: "bar",
fn: function () {
return [_c('span', [_v("Hello")])]
},
proxy: true
} : null], null, true)
})
}
}
Обратите внимание здесь_uВнутри находится непосредственно тернарное выражение, которое читается_uпроисходит в родительском компоненте_render, то подкомпонент не может собрать этоshowзависимый, так сказатьshowОбновление вызовет только обновление родительского компонента, так как же в этом случае повторно выполняется дочерний компонент?$scopedSlotКак насчет функции и повторного рендеринга?
У нас уже есть некоторые предварительные знания:Детализация обновления Vue,знаниеVueКомпоненты не递归更新Да, ноslotScopesВыполнение функции происходит в дочернем компоненте, и родительский компонент должен каким-то образом уведомлять дочерний компонент о необходимости обновления при его обновлении.
На самом деле этот процесс происходит при повторном рендеринге родительского компонента.patchVnodeв, прибылtestкомпонентpatchпроцесс, введенныйupdateChildComponentПосле этой функции она перейдет к проверке своегоslotтак или иначе稳定да, очевидноv-ifконтрольslotочень нестабилен.
const newScopedSlots = parentVnode.data.scopedSlots
const oldScopedSlots = vm.$scopedSlots
const hasDynamicScopedSlot = !!(
(newScopedSlots && !newScopedSlots.$stable) ||
(oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||
(newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key)
)
// Any static slot children from the parent may have changed during parent's
// update. Dynamic scoped slots may also have changed. In such cases, a forced
// update is necessary to ensure correctness.
const needsForceUpdate = !!hasDynamicScopedSlot
if (needsForceUpdate) {
// 这里的 vm 对应 test 也就是子组件的实例,相当于触发了子组件强制渲染。
vm.$forceUpdate()
}
Вот некоторые меры по оптимизации, если не сказать, что пока естьslotScopeЭто вызовет принудительное обновление дочернего компонента.
Есть три ситуации, которые заставят запустить обновление дочернего компонента:
-
scopedSlotsВверх$stableсобственностьfalse
Я всю жизнь следовал этой логике и, наконец, нашел это$stableда_uто естьresolveScopedSlotsопределяется третьим параметром функции, благодаря этому_uгенерируется компиляторомrenderсгенерированная функция, затем перейдите кcodegenПосмотрите в логике:
let needsForceUpdate = el.for || Object.keys(slots).some(key => {
const slot = slots[key]
return (
slot.slotTargetDynamic ||
slot.if ||
slot.for ||
containsSlotChild(slot) // is passing down slot from parent which may be dynamic
)
})
Проще говоря, когда используется какой-либо динамический синтаксис, дочерний компонент будет уведомлен об этом абзаце.scopedSlotsСделайте принудительное обновление.
- Слишком
$stableимущественный, старыйscopedSlotsнестабильный
Это легко понять, старыйscopedSlotsЕсли вам нужно принудительно обновить, вы должны принудительно обновить после рендеринга.
- Старый
$keyне равно новому$key
Эта логика более интересна, вернитесь назад, чтобы увидеть$keyпоколения, видно, что_uчетвертый параметрcontentHashKey,этоcontentHashKeyвcodegenиспользовать, когдаhashАлгоритм вычисляет строку сгенерированного кода, то есть сгенерированный код этой строки функций字符串Изменено, нужно принудительно обновить дочерние компоненты.
function hash(str) {
let hash = 5381
let i = str.length
while(i) {
hash = (hash * 33) ^ str.charCodeAt(--i)
}
return hash >>> 0
}
Суммировать
После версии Vue 2.6slotа такжеslot-scopeСделали унифицированную интеграцию, сделав их все в виде функций, все слоты можно найти вthis.$scopedSlotsПрямой доступ к нему, что делает нам более удобной разработку продвинутых компонентов.
По оптимизации Vue 2.6 тоже можно сделатьslotОбновление не запускает рендеринг родительского компонента и использует ряд умных суждений и алгоритмов, чтобы максимально избежать ненужного рендеринга. (В версии 2.5, начиная с сборкиslotОбласть действия находится в родительском компоненте, поэтому очевидно, что это слот дочернего компонента.slotОбновление будет обновлено с родительским компонентом)
Прослушивая речь Йоды ранее, Vue3 будет больше использовать статические функции шаблонов, чтобы делать больше.预编译优化, в процессе генерации кода в статье мы уже ощутили его усилия в этом направлении, и мы очень рассчитываем на более мощную производительность, которую принесет Vue3.
❤️Спасибо всем
1. Если эта статья была вам полезна, пожалуйста, поддержите ее лайком, ваш "лайк" - движущая сила моего творчества.
2. Подпишитесь на официальный аккаунт «Front-end from advanced to accept», чтобы добавить меня в друзья, я втяну вас в «Front-end группу расширенного обмена», все смогут общаться и добиваться прогресса вместе.