svelte上下文

Context,中文译为上下文,即语境,但从字面入手,恐怕较难理解。

打个比方,假设你30好几还是单身,今天准备去相亲,跟一个男孩/女孩约在南京路中餐厅吃饭,你与相亲对象之间,必然有一个联系人,我们通常称之为“媒婆”,通常嘴角有颗痣,还有媒婆扇子,媒婆就是你们之间(组件之间)能产生联系的上下文

你对相亲对象有任何想要事前了解的信息,都能通过媒婆获悉;对方亦然。

女方一般要深入了解两类重要信息,分别是经济层面及哲学层面。

经济层面包括:房、车和钱,这无可厚非,不一定就说明此人物质,对此愤慨的一般是些刚毕业到30左右的小年青,如果你刚好全部都没有,但刚好女方看重这些,你就暂时不要强求了。

哲学层面包括:掉水里救谁之类的意识形态问题,这才是真正的麻烦,通常手尾很长。

男方那边通常就是肤浅的看漂不漂亮,较为理性的男生不一定只看外表,同样建议还有一个需要了解的哲学问题就是:生气时会不会摔东西?一般是房门、锅碗之类的无辜器物。

当然男人生气也可能摔门。即使外表中等,但决不摔门,就娶了吧,这辈子错不了。

一、组件上下文

1、setContext 及 getContext

组件 Context(上下文) API 为组件提供一种彼此 '对话' 机制,而无需将数据和函数作为属性来传递,或者发送大量的事件。

这是一项高级功能,但是很有用。

Context 相关的 API 函数有两个,分别是setContextgetContext

如果组件调用了setContext(key, context),那么其任意的子组件 都可以通过const context = getContext(key)来获取这个 context。

先来写好媒婆:

MeiPo.svelte

<script>
  import { setContext } from 'svelte'
  import Boy from './Boy.svelte'
  import Girl from './Girl.svelte'
	
  setContext('boy', { car: 1, house: 0, money: 500000 })
  setContext('girl', { slam: true, pretty: true })
</script>

<Boy />
<hr />
<Girl />

接着是男生和女生(不搞哲学属性了):

Boy.svelte

<script>
  import { getContext } from 'svelte'
	
  let girl = getContext('girl')
</script>

<pre>
  Boy 看女生:
  -> 女生是否摔门?({girl.slam})
  -> 女生是否漂亮?({girl.pretty})
</pre>

Girl.svelte

<script>
  import { getContext } from 'svelte'
	
  let boy = getContext('boy')
</script>

<pre>
  Girl 看男生:
  -> 房 ({boy.house}套)
  -> 车 ({boy.car}辆)
  -> 钱 (¥{boy.money})
</pre>

最后把媒婆 show 出来:

App.svelte

<script>
  import MeiPo from './MeiPo.svelte'
</script>

<MeiPo />

Context 的值可以是任意对象,无所拘束。setContextgetContext类似生命周期函数,只能在组件初始化期间调用。

上述例子可以看出,如果你将 Boy 或者 Girl 组件单独拿出来用的话,会引发错误,因此在 getContext 时应该判断是否有值,如果它不在 MeiPo 的上下文中,它其实是没有值的。

2、Context 的 Key

我们可以使用任何类型的值作为键。

例如可以这样用setContext({}, ...)

使用字符串作为键的不利之处就在于,不同的组件库有可能会意外地使用了同一个组件库。

而使用对象字面量,则可在任何情况下都保证键不会发生冲突(因为对象仅在与自身比较才相等,即{} !== {}"x" === "x"),即使你有跨多组件层次的多个不同 context。

3、Contexts vs Props vs Stores

Contexts 与 Props 都是将在父组件声明的数据传达给子组件,它们唯一不同的是 Props 只能父传子,Contexts 可以父传孙或所有后代,即便中间隔了几代,或者是通过 slot (将在下一章详述)传过来的组件也能 get 得到。

当然,Contexts 同时也是享受不到 Props 那种属性绑定的好处了。

Contexts 和 Stores 如出一辙。它们之间的区别在于:stores 可用于应用 任意 位置;

而 Contexts 仅能用于 组件及其后代,如果要使用一个组件的多个实例,每个实例的状态不希望干扰其他实例的状态,这可能会有所帮助。

实际上你可能会同时使用两者。Context 不是响应式的,因此那些会随时间变化的值,你应该使用 Stores,也即 Context 里某些属性可能是 Stores:

const { these, are, stores } = getContext('key');
Context 默认不具备反应性,如果需要上下文中的值支持反应性,应将值先存入 store,然后将 store 放到上下文中。

二、模块上下文

1、共享代码

到目前为止,在所有示例中,<script> 块均包含在初始化每个组件实例时运行的代码。对于绝大多数组件来说,这就是你所需要的。

有时,你需要在单个组件中的实例之外运行一些代码。例如,你可以同时播放所有这五个音频播放器。假设现在的交互是这么设计的:其中一个播放完毕,则同时也停止其他播放器。

先来实现音频播放器组件:

AudioPlayer.svelte

<script>
  export let src;
  export let title;

  let audio;
  let paused = true;

  function stopOthers() {
    // TODO: 实现停止播放其他
  }
</script>

<article class:playing={!paused}>
  <h2>{title}</h2>
  <audio bind:this={audio} bind:paused 
    on:play={stopOthers} controls {src}>
    <track kind="captions">
  </audio>
</article>

我们拿5首曲子来试试:

App.svelte

<script>
  import AudioPlayer from './AudioPlayer.svelte';
	
  let url = 'https://sveltejs.github.io/assets/music/'
	
  let songs = [
    { src: 'strauss.mp3', title: 'The Blue Danube Waltz' },
    { src: 'holst.mp3', title: 'Mars, the Bringer of War' },
    { src: 'satie.mp3', title: 'Gymnopédie no. 1' },
    { src: 'beethoven.mp3', title: 'Symphony no. 5 in Cm...' },
    { src: 'mozart.mp3', title: 'Requiem in D minor...' },
  ]
</script>

{#each songs as song}
  <AudioPlayer src={url + song.src} title={song.title} />
{/each}

要使“其中一个播放完毕,则同时也停止其他播放器”,那么我们可以在 AudioPlayer.svelte 中,通过声明一个<script context="module">的代码块来实现这一点。

该模块中包含的代码,将在模块第一次运行时只执行一次,而不是每次实例化组件时都重复执行。我们将它放置在AudioPlayer.svelte的顶部:

<script context="module">
  let current;
</script>

现在,无需任何状态管理,组件之间就可以彼此 “交流” 了,看下方最后完善的 stopOthers 函数:

function stopOthers() {
  if (current && current !== audio) {
    current.pause();
  }

  current = audio;
}

2、导出

context="module"代码块中导出的任意内容,都将成为模块本身的导出。如果我们从AudioPlayer.svelte中导出stopAll函数...

AudioPlayer.svelte

<script context="module">
  const elements = new Set();

  export function stopAll() {
    elements.forEach(element => element.pause() });
  }
</script>

...我们可以在App.svelte中导入...

App.svelte

<script>
  import AudioPlayer, { stopAll } from './AudioPlayer.svelte';
</script>

...并在某个事件处理程序中使用它:

<button on:click={stopAll}>
  stop all audio
</button>
我们无法使用默认的导出,因为组件   默认就导出的。

总结

Svelte 中的 Context 概念和 React 和 Vue 没有区别,API 稍微不同,做的是一样的事情。

Svelte 对比 React 稍微要简单直观一些,抛却了 Provider 和 Consumer 的概念,事实上简单些就很好,就想组件间共享点数据罢了,没必要有板有眼创造一些高级隐喻,让其漂染浓厚的设计气息。

模块上下文的主要用途,是提供一种方法,让某个组件的所有实例都共享同一个上下文(这其实用 store 也完全能实现同等功能,只不过模块上下文更显得关联性高一些,以及封装性强一些)。

比方说最常见的是音乐播放器,在播放列表中,点击任意一曲,正在播放的音乐就要立即停止,然后播放选中的那一首,也就是说同一时刻只允许一首歌在播放,这种场景很适合模块上下文。

模块上下文写在 <script context="module"> 这个顶层 script 标记中,这是 Svelte 唯一允许你使用的第二个 顶层 script。

猜你喜欢

转载自blog.csdn.net/ramblerviper/article/details/124942972
今日推荐