vue 组件数据共享_Vue共享组件

本文探讨了Vue框架中实现组件数据共享的方法,通过实例解析如何在不同组件间传递和共享状态,帮助开发者理解Vue的组件通信机制。
摘要由CSDN通过智能技术生成

vue 组件数据共享

As a company, we sell experiences on many different sales channels, gotui.com, musement.com, travel agencies platforms, onsite tour operator web-applications, etc. All of these platforms consume REST APIs and share common business logic: product pages, product listings, search pages, checkout, orders listings, login, account, etc.

作为一家公司,我们在许多不同的销售渠道上销售经验,包括gotui.commusement.com ,旅行社平台,现场旅行社网络应用程序等。所有这些平台都使用REST API并共享通用的业务逻辑:产品页面,产品列表,搜索页面,结帐,订单列表,登录名,帐户等。

Rewriting and maintaining this logic for every project requires a lot of effort so we faced a big challenge: how do we develop blocks that are easy to plug-in but flexible enough to satisfy the different business requirements of our platforms? And how do we sync all these blocks?

为每个项目重写和维护此逻辑都需要大量的工作,因此我们面临一个巨大的挑战:我们如何开发易于插入但足够灵活以满足我们平台不同业务需求的模块? 以及我们如何同步所有这些块?

故事 (The story)

It was an exciting road to reach our current architecture.

这是达到我们当前架构的令人兴奋的道路。

It started after delivering our second web-application: we noticed a lot of copy-paste work and difficulties syncing new features and bug fixes on both applications at the same time.

它是在交付第二个Web应用程序之后开始的:我们注意到很多复制粘贴工作,并且难以同时在两个应用程序上同步新功能和错误修复。

In front of the coffee machine, we started our first meetings and the paper board became the place where we shared ideas and concepts.

在咖啡机前,我们开始了第一次会议,纸板成为了我们分享想法和概念的地方。

During Tech-Fridays, we developed the first PoCs, and finally, step by step, the architecture came out naturally and, more important, every member of the team contributed to it.

在Tech-Friday期间,我们开发了第一个PoC,最后,一步一步地,架构自然而然地诞生了,更重要的是,团队的每个成员都为此做出了贡献。

We ended up with a NuxtJS application that consumes Vue components imported as npm packages. Like LEGO® Technic™ kits, given a set of customizable blocks (shared-components), we can build many different vehicles (our platforms).

我们最终得到了一个NuxtJS应用程序,该应用程序使用作为npm软件包导入的Vue组件。 与乐高®Technic™套件一样,给定一组可定制的块(共享组件),我们可以制造许多不同的车辆(我们的平台)。

汇总构建 (Building with Rollup)

Having customizable components means that not all features and blocks are always used, so we need to be sure that only the necessary parts are imported. To do that we rely on the tree-shaking feature that removes unused code from the final bundled files.

具有可定制的组件意味着并非总是使用所有功能和块,因此我们需要确保仅导入必要的部分。 为此,我们依赖于摇树功能,该功能可从最终的捆绑文件中删除未使用的代码。

Since NuxtJS uses Webpack as the final bundling tool, the only way to allow Webpack to efficiently perform the tree-shaking is to provide ESM modules. Given that, we choose to build our shared-components with Rollup: Webpack will provide that type of output only with Version 5. Moreover, it’s very easy to understand what Rollup is doing under the hood and thanks to that writing custom plugins was really straightforward.

由于NuxtJS使用Webpack作为最终的捆绑工具,因此允许Webpack有效执行摇树的唯一方法是提供ESM模块 。 鉴于此,我们选择使用Rollup构建共享组件:Webpack仅在Version 5中提供该类型的输出。 此外,很容易理解Rollup在后台进行的操作,而且由于编写自定义插件非常简单。

Another benefit of Rollup t is that in our context the bundled files are 30–40% smaller compared to the Webpack ones.

Rollup t的另一个好处是,在我们的上下文中,捆绑的文件比Webpack的文件小30–40%。

通过商店沟通 (Communication via store)

Our shared-components expose different blocks so that the consuming application (we call it ‘orchestrator’) can choose to use only the ones required.

我们的共享组件公开了不同的块,因此使用中的应用程序(我们称为“协调器”)可以选择仅使用所需的那些。

Imagine a search-component: it exposes a listing, a filters block, and a search bar; in our orchestrator, we can decide to use only the listing and the input:

想象一下搜索组件:它公开一个清单,一个过滤器块和一个搜索栏; 在我们的协调器中,我们可以决定仅使用清单和输入:

import { SearchListing, SearchBar } from ‘@musement/search-component’;

But if they’re different components how do they sync the data? The search-component uses a vuex store module that is registered inside the orchestrator thanks to the registerModule method.

但是,如果它们是不同的组件,则如何同步数据? 搜索组件使用vuex存储模块,该模块在orchestrator内部注册,这要归功于registerModule方法

orchestratorStore.registerModule(‘searchStoreNamespace’, searchComponentStoreModule, { preserveState: false });

We set the preserveState to false because the search-component has its own store default state and the consumer knows nothing about it.

我们将preserveState设置为false,因为搜索组件具有其自己的商店默认状态,而使用者对此一无所知。

After the registration the SearchBar dispatches an action that fetches the data and stores the response:

注册后, SearchBar调度一个操作,该操作将获取数据并存储响应:

<template>
<form v-on:submit.prevent="onSubmit">
<input v-model="text" placeholder="Search for activities…" />
<button type="submit">Search</button>
</form>
</template>...export default Vue.extend({
name: 'SearchBar',
data() {
return {
text: ''
}
},
methods: {
onSubmit() {
this.$store.dispatch(
'searchStoreNamespace/fetchActivities',
{ this.text }
);
},
},
})

After the API response the SearchListing is able to show the fetched activities:

API响应后, SearchListing能够显示提取的活动:

<template>
<ul>
<li v-for="activity in activities" :key="activity.uuid">
{activity.name}
</li>
</ul>
</template>...
export default Vue.extend({
name: 'SearchListing',
computed: {
...mapGetters('searchStoreNamespace', [ 'activities' ]),
},
})

建立 (Setup)

Since our shared-components can be composed by different blocks that communicate via a vuex-store module, passing Vue props directly from the orchestrator to the components would lead us to a lot of code duplication. For that reason, we decided to expose a setup method where we set customizable data directly inside the store, avoiding all the parent-child tree traversing.

由于我们的共享组件可以由通过vuex-store模块进行通信的不同块组成,因此将Vue道具直接从协调器传递到组件将导致我们重复很多代码。 因此,我们决定公开一种设置方法,在该方法中,我们直接在商店内部设置可自定义的数据,避免遍历所有父子树。

import { setup } from '@musement/search-component';
setup(consumerStoreInstance, {
apiBaseURL: 'https://api.musement.com',
language: 'en-GB',
currency: 'GBP',
...
})

We need the consumerStoreInstance because the store module registration is performed inside the shared-component itself so that the consumer doesn’t need to be aware of that.

我们需要ConsumerStoreInstance,因为商店模块注册是在共享组件本身内部执行的,因此消费者不需要知道这一点。

NuxtJs的服务器端渲染 (Server-Side Rendering with NuxtJs)

Due to SEO requirements, most of our applications use Server-Side Rendering provided by NuxtJS.

由于SEO的要求,我们的大多数应用程序都使用NuxtJS提供的服务器端渲染

In those cases, we need to display crawlers relevant data after an API call so we expose an initialDispatch function that is called inside the NuxtJS fetch method.

在这些情况下,我们需要在API调用之后显示搜寻器的相关数据,以便公开在NuxtJS fetch方法内部调用的initialDispatch函数。

After that, we need to ensure that the vuex-store gets hydrated browser side with the same new data. We do that with a hydrate method that registers the store module on the client too.

之后,我们需要确保vuex存储在浏览器端具有相同的新数据。 我们使用可在客户端上注册存储模块的hydrate方法来做到这一点。

import { initialDispatch, hydrate } from '@musement/activity-component';export default {
name: 'ActivityPage',
async fetch({ store }) {
const activityUuid = store.state.activityPage.uuid;
initialDispatch(store, activityUuid);
},
beforeCreate() {
if (process.client) {
hydrate(this.$store);
}
}
}

事件总线 (Event-bus)

As we said before our shared-components are black-boxes, but imagine we have our imported SearchListing, how do we inform the orchestrator that client-side navigation needs to be performed?

如前所述,共享组件是黑匣子,但是假设我们导入了SearchListing ,如何通知协调器需要执行客户端导航?

The way we communicate from the shared-components to the consumers is via an event-bus with a documented API. We create and expose it inside the shared-component:

我们从共享组件与使用者进行通信的方式是通过带有文档化API的事件总线。 我们在共享组件内部创建并公开它:

import Vue from 'vue';
const eventBus = new Vue();
export default {
$emit(eventName, eventPayload) {
eventBus.$emit(eventName, eventPayload);
},
$on(eventName, eventHandler) {
eventBus.$on(eventName, eventHandler);
},
$off(eventName, eventHandler) {
eventBus.$off(eventName, eventHandler);
},
};

Inside the listing, we $emit a Vue event.

里面的上市,我们$ EMIVue的事件

<template>
<ul>
<li v-for="activity in activities" :key="activity.uuid">
<h3>
<a
:href="activity.url"
@click.prevent="onActivityClick(activity.uuid)">
{activity.name}
</a>
</h3>
</li>
</ul>
</template>
...
import EventBusSearch from './eventBus';export default Vue.extend({
name: 'SearchListing',
computed: {
...mapGetters('searchStoreNamespace', [ 'activities' ]),
},
methods: {
onActivityClick(uuid: string) {
EventBusSearch.$emit('onNavigation', { uuid });
}
},
})

And finally, we listen to that event inside the orchestrator and remove it when the page will be destroyed.

最后,我们在协调器内部侦听该事件,并在页面被破坏时将其删除。

import { SearchListing, EventBusSearch } from '@musement/search-component';export default {
name: 'SearchPage',
...
beforeMount() {
EventBusSearch.$on('onNavigation', this.onNavigation);
},
beforeDestroy() {
EventBusSearch.$off('onNavigation', this.onNavigation);
},
methods: {
onNavigation({ uuid }) {
this.$router.push({ name: 'activity', params: { uuid } });
}
}
}

CSS主题 (CSS theming)

As blocks plugged inside different applications, our shared-components need to adapt to the overall look-and-feel of the context, especially to the colours palette.

随着将块插入不同的应用程序中,我们的共享组件需要适应上下文的整体外观,尤其是调色板。

To achieve that, we accept a theme property inside the config argument of the setup function:

为此,我们在setup函数的config参数中接受一个theme属性:

import { setup } from '@musement/search-component';
setup(consumerStoreInstance, {
theme: {
name: 'musement',
vars: {
'--fontPrimary': 'Gill Sans, sans-serif;',
},
},
...
})

With the name property, the consumer can use one of the premade themes, and with the vars prop, it can override specific CSS variables; both are optional.

使用name属性,使用者可以使用预制主题之一,而使用vars prop可以覆盖特定CSS变量。 两者都是可选的。

Then, inside the shared-component, we set the theme in the store via a mutation, with a default fallback (in this case the theme ‘musement’):

然后,在共享组件内部,我们通过突变在商店中设置了主题,并具有默认后备(在本例中为主题“ musement”):

const themes = {
musement: {
'--primaryColor': 'red',
'--fontPrimary': 'Arial, sans-serif',
},
blueTheme: {
'--primaryColor': 'blue',
'--fontPrimary': 'Tahoma, sans-serif',
}
}
export default {
[SET_THEME](state, theme) {
state.theme = {
...themes[theme.name || 'musement'],
...(theme.vars || {}),
}
},
}

Finally, we apply the CSS variables directly to the root element of the shared-component:

最后,我们将CSS变量直接应用于共享组件的根元素:

<template>
<div class="activityComponent" :style="theme">
<h1>{ activity.title }</h1>
...
</div>
</template>
...
export default Vue.extend({
name: 'activityComponent',
computed: {
...mapGetters('searchStoreNamespace', [ 'theme', 'activity' ]),
},
})...<style lang="scss">.activityComponent {
font-family: var(--fontPrimary); h1 {
color: var(--primaryColor);
}
}</style>

结论 (Conclusion)

As you’ve probably noticed here we’ve only scraped the surface of the possibilities and we know, as a frontend team, that we’re only at the beginning of a long and thrilling journey; we are still faced with many challenges, like:

正如您可能已经在这里注意到的那样,我们只是探讨了可能性的表面,并且我们知道,作为前端团队,我们才刚刚开始漫长而激动的旅程; 我们仍然面临许多挑战,例如:

  • Efficiently manage shared-components inside shared-components

    有效管理共享组件内部的共享组件
  • Dynamic import based on feature flags

    基于功能标记的动态导入
  • Properly take in sync dependencies across the different platforms

    在不同平台上正确获取同步依赖项
  • Develop a light CSS library to reduce styles duplication and obtain layout consistency between our applications

    开发一个轻量级CSS库以减少样式重复并在我们的应用程序之间获得布局一致性

But these challenges are there to let us learn day after day better ways to provide valuable applications for our customers and, hopefully, contribute to the developer’s community with shareable solutions.

但是这些挑战使我们日复一日学习更好的方法来为我们的客户提供有价值的应用程序,并希望通过可共享的解决方案为开发人员社区做出贡献。

See you soon with the next chapter of this story, stay tuned!

很快就会看到这个故事的下一章,敬请期待!

翻译自: https://medium.com/tuidx/vue-shared-components-2c40973c32c5

vue 组件数据共享

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值