предисловие
Когда-то концепция высокоуровневых компонентов была очень популярна в React, но она мало обсуждалась в сообществе Vue.Эта статья действительно поможет вам сыграть в продвинутую операцию.
Позвольте мне сначала сказать вам, что суть этой статьи состоит в том, чтобы изучить этот тип мышления, то есть智能组件
а также木偶组件
Развязка, услышанная об этом понятии, не имеет значения, далее объясню подробно.
Это можно сделать разными способами, напримерslot-scopes
, например, будущееcomposition-api
. Код, написанный в этой статье, не рекомендуется использовать в производственной среде. В производственной среде есть более зрелые библиотеки для использования. В этой статье подчеркивается, что思想
, кстати, пересадить геймплей сообщества React на скин.
Не брызгай на меня, не брызгай на меня, не брызгай на меня! !Эта статья предназначена только для демонстрации идеи компонентов высокого уровня.Если вы хотите упростить асинхронное управление состоянием, упомянутое в статье, в реальном бизнесе, используйтеslot-scopes
библиотека с открытым исходным кодомvue-promised
Также упоминается в названии20k
На самом деле это немного заглавная вечеринка.Что я хочу выразить больше, так это то, что у нас должен быть такой дух.Только это умение точно не позволит вам достичь20k
. Но я верю, что до тех пор, пока у всех есть дух изучения расширенного использования, оптимизации бизнес-кода и повышения эффективности, мы всегда будем этого добиваться, и этот день не за горами.
пример
В этой статье используются наиболее распространенные требования в обычной разработке, то есть异步数据的请求
Например, начнем с написания обычного плеера:
<template>
<div v-if="error">failed to load</div>
<div v-else-if="loading">loading...</div>
<div v-else>hello {{result.name}}!</div>
</template>
<script>
export default {
data() {
return {
result: {
name: '',
},
loading: false,
error: false,
},
},
async created() {
try {
// 管理loading
this.loading = true
// 取数据
const data = await this.$axios('/api/user')
this.data = data
} catch (e) {
// 管理error
this.error = true
} finally {
// 管理loading
this.loading = false
}
},
}
</script>
Обычно мы пишем так, и обычно не чувствуем никакой проблемы, но на самом деле каждый раз, когда мы пишем асинхронные запросы, мы должны иметьloading
,error
статус, необходимо иметь取数据
логика, и управлять этими состояниями.
Итак, придумайте способ абстрагировать его? Кажется, что хороших способов не так уж и много.До того, как Hook стал популярным, сообщество React часто использовалоHOC
(компонент высокого порядка) — это компонент высокого порядка для работы с такой абстракцией.
Что такое компоненты высшего порядка?
Здесь мы должны думать о компоненте более высокого порядка, в конце концов, это то, что понятие, на самом деле, в конечном счете, является компонентами высокого порядка:
一个函数接受一个组件为参数,返回一个包装后的组件
.
в реакции
В React компонентыClass
, поэтому компоненты более высокого порядка иногда используют装饰器
синтаксис для достижения, потому что装饰器
Суть также в том, чтобы принятьClass
вернуть новыйClass
.
В мире React компоненты более высокого порядкаf(Class) -> 新的Class
.
во Вью
В мире Vue компонент — это объект, поэтому компонент более высокого порядка — это функция, которая принимает объект и возвращает новый обернутый объект.
По аналогии с миром Vue компоненты более высокого порядкаf(object) -> 新的object
.
Смарт-компоненты и кукольные компоненты
Если вы еще не знаете木偶
компоненты и智能
Концепция компонентов, позвольте мне дать вам краткое введение, это очень зрелая концепция в сообществе React.
木偶
Компонент: как у марионетки, только на основе внешнего входящегоprops
для отображения соответствующего представления, независимо от того, откуда были получены данные.
智能
Компоненты: обычно упакованы в木偶
Вне компонента данные получаются через запросы и т.п. и передаются в木偶
компонент, который управляет его рендерингом.
В общем случае их структурная взаимосвязь выглядит следующим образом:
<智能组件>
<木偶组件 />
</智能组件>
У них есть другой псевдоним, который容器组件
а такжеui组件
, не очень изображение.
выполнить
Специально в приведенном выше примере (если вы забудете, вернемся и посмотрите, ха-ха), наше мышление это,
- Компоненты более высокого порядка принимают
木偶组件
а также请求的方法
как параметр - существует
mounted
Данные запрашиваются в жизненном цикле - Передайте запрошенные данные через
props
Перейти к木偶组件
.
Следующим шагом является реализация этой идеи. Впервые упомянутая выше,HOC
Это функция, на этот раз наше требование — реализовать управление запросами.HOC
, затем сначала определите, чтобы он принимал два параметра, мы помещаем этоHOC
называетсяwithPromise
.
а такжеloading
,error
состояние ожидания и加载中
,加载错误
Дождитесь соответствующего представления, мы должны新返回的包装组件
, то есть в следующей функцииreturn 的那个新的对象
определено в.
const withPromise = (wrapped, promiseFn) => {
return {
name: "with-promise",
data() {
return {
loading: false,
error: false,
result: null,
};
},
async mounted() {
this.loading = true;
const result = await promiseFn().finally(() => {
this.loading = false;
});
this.result = result;
},
};
};
В параметре:
-
wrapped
То есть объект компонента должен быть обернут. -
promiseFunc
То есть функция, соответствующая запросу, должна вернуть Promise.
Выглядит хорошо, но в функции мы не можем.vue
написать в один файлtemplate
Напишите шаблон так,
Но мы знаем, что шаблон в конечном итоге компилируется в объект-компонент.render
функция, то мы можем написать это напрямуюrender
функция. (注意,本例子是因为便于演示才使用的原始语法,脚手架创建的项目可以直接用jsx
грамматика. )
в этотrender
В функцию мы передаем входящийwrapped
То есть компоненты марионетки завернуты.
Это формы智能组件获取数据
-> 木偶组件消费数据
, такие потоки данных.
const withPromise = (wrapped, promiseFn) => {
return {
data() { ... },
async mounted() { ... },
render(h) {
return h(wrapped, {
props: {
result: this.result,
loading: this.loading,
},
});
},
};
};
На данный момент это уже едва пригодный для использования прототип, давайте сделаем заявление木偶
компоненты.
Это на самом деле逻辑和视图分离
идея.
const view = {
template: `
<span>
<span>{{result?.name}}</span>
</span>
`,
props: ["result", "loading"],
};
Обратите внимание, что компоненты здесь могут быть любыми..vue
файл, я просто использую этот способ записи здесь для простоты.
Потом произошло что-то волшебное, не моргай, мы использовалиwithPromise
завернуть этоview
компоненты.
// 假装这是一个 axios 请求函数
const request = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ name: "ssh" });
}, 1000);
});
};
const hoc = withPromise(view, request)
Затем визуализируйте его в родительском компоненте:
<div id="app">
<hoc />
</div>
<script>
const hoc = withPromise(view, request)
new Vue({
el: 'app',
components: {
hoc
}
})
</script>
В этот момент компонент отображает мое имя после того, как в течение секунды остается пустым.ssh
, проходит весь асинхронный поток данных.
теперь добавляю加载中
а также加载失败
Просмотр делает взаимодействие более дружественным.
const withPromise = (wrapped, promiseFn) => {
return {
data() { ... },
async mounted() { ... },
render(h) {
const args = {
props: {
result: this.result,
loading: this.loading,
},
};
const wrapper = h("div", [
h(wrapped, args),
this.loading ? h("span", ["加载中……"]) : null,
this.error ? h("span", ["加载错误"]) : null,
]);
return wrapper;
},
};
};
Код пока можно найти наПредварительный просмотр эффектаВы также можете просмотреть исходный код непосредственно в исходниках консоли.
Полный
Хотя компоненты высокого уровня до сих пор можно продемонстрировать, они не завершены, и в нем по-прежнему отсутствуют некоторые функции, такие как
- Чтобы получить параметры, определенные в дочернем компоненте, как параметры исходного запроса на отправку.
- Прослушивать изменения параметров запроса в подкомпонентах и повторно отправлять запрос.
- внешние компоненты, переданные
hoc
Параметры компонента теперь не передаются.
Первый пункт понять несложно, параметры запрашиваемой сцены очень гибкие.
Второй пункт также является общим требованием в практических сценариях.
Третий момент заключается в том, что во избежание непонимания некоторых студентов, здесь более подробно, например, мы используем его в самом внешнем слое.hoc
компонент, вы можете передать некоторые дополнительныеprops
илиattrs
Четное插槽slot
до самого сокровенного木偶
компоненты. Такhoc
В качестве моста компонент должен взять на себя ответственность за его прозрачную передачу.
Для достижения первого пункта мы согласилисьview
Конкретный компонент должен быть установлен на компонентеkey
Поле как параметр запроса, например, здесь мы договорились, что оно называетсяrequestParams
.
const view = {
template: `
<span>
<span>{{result?.name}}</span>
</span>
`,
data() {
// 发送请求的时候要带上它
requestParams: {
name: 'ssh'
}
},
props: ["result", "loading"],
};
переписать нашrequest
функцию, готовя ее к приему аргументов,
и пусть это响应数据
вернуть как есть请求参数
.
// 假装这是一个 axios 请求函数
const request = (params) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(params);
}, 1000);
});
};
Так что теперь вопрос в том, как намhoc
компонентыview
стоимость компонента,
Как мы обычно получаем экземпляры дочерних компонентов? Вот такref
, также используйте его здесь:
const withPromise = (wrapped, promiseFn) => {
return {
data() { ... },
async mounted() {
this.loading = true;
// 从子组件实例里拿到数据
const { requestParams } = this.$refs.wrapped
// 传递给请求函数
const result = await promiseFn(requestParams).finally(() => {
this.loading = false;
});
this.result = result;
},
render(h) {
const args = {
props: {
result: this.result,
loading: this.loading,
},
// 这里传个 ref,就能拿到子组件实例了,和平常模板中的用法一样。
ref: 'wrapped'
};
const wrapper = h("div", [
this.loading ? h("span", ["加载中……"]) : null,
this.error ? h("span", ["加载错误"]) : null,
h(wrapped, args),
]);
return wrapper;
},
};
};
Для выполнения второго пункта, при изменении параметров запроса дочернего компонента, родительскому компоненту также необходимо响应式
повторно отправить запрос и передать новые данные дочернему компоненту.
const withPromise = (wrapped, promiseFn) => {
return {
data() { ... },
methods: {
// 请求抽象成方法
async request() {
this.loading = true;
// 从子组件实例里拿到数据
const { requestParams } = this.$refs.wrapped;
// 传递给请求函数
const result = await promiseFn(requestParams).finally(() => {
this.loading = false;
});
this.result = result;
},
},
async mounted() {
// 立刻发送请求,并且监听参数变化重新请求
this.$refs.wrapped.$watch("requestParams", this.request.bind(this), {
immediate: true,
});
},
render(h) { ... },
};
};
Третий пункт свойств прозрачной передачи, нам нужно только поставить$attrs
,$listeners
,$scopedSlots
Просто передайте это,
здесь$attrs
свойство, объявленное во внешнем шаблоне,$listeners
функция слушателя, объявленная во внешнем шаблоне,
Возьмите этот пример:
<my-input value="ssh" @change="onChange" />
Внутри компонента вы можете получить такую структуру:
{
$attrs: {
value: 'ssh'
},
$listeners: {
change: onChange
}
}
Обратите внимание, что прохождение$attrs
,$listeners
Требование возникает не только в высокоуровневых компонентах, обычно если мы хотимel-input
Этот компонент инкапсулирует слой вmy-input
Если вы хотите объявить один за другимel-input
принятыйprops
, это утомительно, прямая передача$attrs
,$listeners
Вот такel-input
Внутри все переданные параметры все еще могут быть обработаны.
// my-input 内部
<template>
<el-input v-bind="$attrs" v-on="$listeners" />
</template>
затем вrender
В функцию его можно прозрачно передать так:
const withPromise = (wrapped, promiseFn) => {
return {
...,
render(h) {
const args = {
props: {
// 混入 $attrs
...this.$attrs,
result: this.result,
loading: this.loading,
},
// 传递事件
on: this.$listeners,
// 传递 $scopedSlots
scopedSlots: this.$scopedSlots,
ref: "wrapped",
};
const wrapper = h("div", [
this.loading ? h("span", ["加载中……"]) : null,
this.error ? h("span", ["加载错误"]) : null,
h(wrapped, args),
]);
return wrapper;
},
};
};
На данный момент реализован полный код:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>hoc-promise</title>
</head>
<body>
<div id="app">
<hoc msg="msg" @change="onChange">
<template>
<div>I am slot</div>
</template>
<template v-slot:named>
<div>I am named slot</div>
</template>
</hoc>
</div>
<script src="./vue.js"></script>
<script>
var view = {
props: ["result"],
data() {
return {
requestParams: {
name: "ssh",
},
};
},
methods: {
reload() {
this.requestParams = {
name: "changed!!",
};
},
},
template: `
<span>
<span>{{result?.name}}</span>
<slot></slot>
<slot name="named"></slot>
<button @click="reload">重新加载数据</button>
</span>
`,
};
const withPromise = (wrapped, promiseFn) => {
return {
data() {
return {
loading: false,
error: false,
result: null,
};
},
methods: {
async request() {
this.loading = true;
// 从子组件实例里拿到数据
const { requestParams } = this.$refs.wrapped;
// 传递给请求函数
const result = await promiseFn(requestParams).finally(() => {
this.loading = false;
});
this.result = result;
},
},
async mounted() {
// 立刻发送请求,并且监听参数变化重新请求
this.$refs.wrapped.$watch(
"requestParams",
this.request.bind(this),
{
immediate: true,
}
);
},
render(h) {
const args = {
props: {
// 混入 $attrs
...this.$attrs,
result: this.result,
loading: this.loading,
},
// 传递事件
on: this.$listeners,
// 传递 $scopedSlots
scopedSlots: this.$scopedSlots,
ref: "wrapped",
};
const wrapper = h("div", [
this.loading ? h("span", ["加载中……"]) : null,
this.error ? h("span", ["加载错误"]) : null,
h(wrapped, args),
]);
return wrapper;
},
};
};
const request = (data) => {
return new Promise((r) => {
setTimeout(() => {
r(data);
}, 1000);
});
};
var hoc = withPromise(view, request);
new Vue({
el: "#app",
components: {
hoc,
},
methods: {
onChange() {},
},
});
</script>
</body>
</html>
допустимыйздесьПредварительный просмотр эффекта кода.
Мы разрабатываем новые компоненты, просто беритеhoc
Просто приходите и используйте его повторно, его ценность для бизнеса будет отражена, а код будет упрощен до невообразимой степени.
import { getListData } from 'api'
import { withPromise } from 'hoc'
const listView = {
props: ["result"],
template: `
<ul v-if="result>
<li v-for="item in result">
{{ item }}
</li>
</ul>
`,
};
export default withPromise(listView, getListData)
Все становится просто и элегантно.
комбинация
Обратите внимание, что эта глава может быть трудной для студентов, которые не знакомились с разработкой React, поэтому вы можете прочитать ее правильно или пропустить ее в первую очередь.
Однажды мы вдруг очень обрадовались и написали высокоуровневый компонент под названиемwithLog
, это так же просто, какmounted
Цикл объявления помогает распечатать журнал.
const withLog = (wrapped) => {
return {
mounted() {
console.log("I am mounted!")
},
render(h) {
return h(wrapped)
},
}
}
Здесь мы находим это сноваon
,scopedSlots
На самом деле довольно сложно дождаться извлечения и передачи атрибута.this
Интегрируйте функции, требующие прозрачных атрибутов:
function normalizeProps(vm) {
return {
on: vm.$listeners,
attr: vm.$attrs,
// 传递 $scopedSlots
scopedSlots: vm.$scopedSlots,
}
}
затем вh
Второй параметр можно извлечь и передать.
const withLog = (wrapped) => {
return {
mounted() {
console.log("I am mounted!")
},
render(h) {
return h(wrapped, normalizeProps(this))
},
}
}
Затем заверните его вhoc
Помимо:
var hoc = withLog(withPromise(view, request));
Видно, что такая вложенность — совсем головная боль, ставимredux
это карриcompose
Функция перемещена сюда, этоcompose
Функции, по сути, постоянно делают функции более высокого порядка и возвращают новую функцию.
функциональный состав
function compose(...funcs) {
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
compose(a, b, c)
Возвращается новая функция, эта функция будет проходить через несколько функций嵌套执行
Сигнатура возвращаемой функции:(...args) => a(b(c(...args)))
Эта функция может занять много времени у начинающих студентов, потому что она действительно очень сложная, но как только вы ее поймете, ваше функциональное мышление выйдет на новый уровень.
Я будуgithub
для многопараметрическогоcompose
В примере сделал пошаговый разбор разборки, если интересно можете глянутьСоставьте принцип разборки
круговой состав
Если вы этого не понимаете函数式
изcompose
Метод записи, далее мы используем обычный цикл для записи, то есть возвращаем функцию, выполняем входящий массив функций справа налево, а возвращаемое значение предыдущей функции будет использоваться как параметр следующего выполнения функции.
нормально написаноcompose
Функция такова:
function compose(...args) {
return function(arg) {
let i = args.length - 1
let res = arg
while(i >= 0) {
let func = args[i]
res = func(res)
i--
}
return res
}
}
Модернизация с помощью Promise
Но это также означает, что нам нужно преобразоватьwithPromise
Функции высшего порядка, т.к. внимательно наблюдайте за этимcompose
, который оборачивает функцию так, что она принимает аргумент, и оборачивает первую функцию.返回值
Передано следующей функции в качестве параметра.
Напримерcompose(a, b)
Сказать,b(arg)
Возвращаемое значение будетa
параметры, дальнейшие вызовыa(b(args))
Это необходимо для того, чтобы функции, принятые в compose, имели только один параметр для каждого элемента..
Затем, согласно этой идее, преобразуемwithPromise
, на самом деле, чтобы еще больше его улучшить, пусть он возвращает функцию, которая принимает только один параметр:
const withPromise = (promiseFn) => {
// 返回的这一层函数 wrap,就符合我们的要求,只接受一个参数
return function wrap(wrapped) {
// 再往里一层 才返回组件
return {
mounted() {},
render() {},
}
}
}
С его помощью вы можете более элегантно комбинировать компоненты более высокого порядка:
const compsosed = compose(
withPromise(request),
withLog,
)
const hoc = compsosed(view)
надcompose
Полный код главыВ этот.
Обратите внимание, что нередко вы не понимаете эти концепции в первый раз в этом разделе.Они очень популярны в сообществе React, но редко обсуждаются в сообществе Vue! об этомcompose
Функция, когда я сначала наткнулся на него в сообществе React, я не мог понять это вообще. Не слишком поздно понять его использование первым.
Реальная деловая сцена
Многие могут подумать, что приведенный выше код не имеет большого практического значения, ноvue-router
изРасширенная документация по использованиюСуществует реальная сцена, где компоненты высокого порядка используются для решения проблем.
Сначала кратко опишите сценарий, который мы знаемvue-router
Асинхронную маршрутизацию можно настроить, но когда скорость сети очень низкая, эта асинхронная маршрутизация соответствуетchunk
То есть код компонента, который не будет прыгать, пока не завершится загрузка.
этот абзац下载异步组件
время, когда мы хотим, чтобы страница отображалаLoading
Компоненты, делающие взаимодействие более дружественным.
существуетДокументация Vue — асинхронные компонентыВ этой главе хорошо видно, что Vue поддерживает объявление асинхронных компонентов.loading
Соответствующий компонент рендеринга:
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
Попробуем написать этот код какvue-router
, перепишите исходный асинхронный маршрут:
new VueRouter({
routes: [{
path: '/',
- component: () => import('./MyComponent.vue')
+ component: AsyncComponent
}]
})
Вы обнаружите, что он вообще не поддерживается, и выполните его тщательную отладку.vue-router
Исходный код найден,vue-router
Внутренний разбор асинхронных компонентов иvue
Обработка представляет собой два совершенно разных набора логики, вvue-router
Реализация не поможет вам отрендеритьLoading
компоненты.
Это не должно составить труда для остроумных общественных лидеров, давайте изменим образ мышления, давайтеvue-router
перейти к容器组件
,это容器组件
Помогите нам использовать внутренний механизм рендеринга Vue для рендерингаAsyncComponent
, иначе это может быть отображеноloading
положение дел? Конкретный код выглядит следующим образом:
Благодаря vue-routercomponent
поле принимаетPromise
, поэтому мы используем компонентPromise.resolve
Оберните один слой.
function lazyLoadView (AsyncView) {
const AsyncHandler = () => ({
component: AsyncView,
loading: require('./Loading.vue').default,
error: require('./Timeout.vue').default,
delay: 400,
timeout: 10000
})
return Promise.resolve({
functional: true,
render (h, { data, children }) {
// 这里用 vue 内部的渲染机制去渲染真正的异步组件
return h(AsyncHandler, data, children)
}
})
}
const router = new VueRouter({
routes: [
{
path: '/foo',
component: () => lazyLoadView(import('./Foo.vue'))
}
]
})
Таким образом, разрыв между загрузкой кода при прыжке, красивыйLoading
Компоненты отображаются на странице.
Суммировать
Весь код для этой статьи хранится вРепозиторий на гитхабе, и предоставитьпредварительный просмотр.
Я хотел бы передать этот документ автору «Vue Technology Insider», который очень помог мне в моем пути изучения исходного кода.hcysun
Большой парень, хотя я еще не разговаривал с ним, когда я был новичком, проработавшим несколько месяцев, я нашел эту статью, подумав о потребностях бизнеса:Исследуйте компоненты высшего порядка Vue | HcySunYang
В то время я не мог понять проблемы с исходным кодом и решения по исправлению, затронутые в этой статье, а затем я переключился на другой способ реализации бизнеса, но вещи, упомянутые в этой статье, не давали мне покоя.После напряженной работы, Я усердно работаю над изучением исходного кода и надеюсь, что однажды смогу полностью понять эту статью.
Теперь я, наконец, могу понять, о чем говорится в статье.$vnode
а такжеcontext
Что это значит, но эта ошибка в версии Vue 2.6 из-заslot
Кстати, реализация была переписана и исправлена, и теперь она использует последнюю версию Vue.slot
Благодаря синтаксису и функциям более высокого порядка ошибки, упомянутые в этой статье, больше не будут встречаться.
❤️Спасибо всем
1. Если эта статья была вам полезна, пожалуйста, поддержите ее лайком, ваш "лайк" - движущая сила моего творчества.
2. Подпишитесь на официальный аккаунт «Front-end from advanced to accept», чтобы добавить меня в друзья, я втяну вас в «Front-end группу расширенного обмена», все смогут общаться и добиваться прогресса вместе.