一文搞懂Svelte,让你知道Svelte和Vue的异同

1,992 阅读4分钟

Svelte vs Vue

官网地址

Vue: cn.vuejs.org/

Svelte: svelte.dev/

是什么

什么是Vue

Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

什么是 Svelte?

Svelte 是一个构建 web 应用程序的工具。

Svelte 与诸如 React 和 Vue 等 JavaScript 框架类似,都怀揣着一颗让构建交互式用户界面变得更容易的心。

但是有一个关键的区别:Svelte 在 构建/编译阶段 将你的应用程序转换为理想的 JavaScript 应用,而不是在 运行阶段 解释应用程序的代码。这意味着你不需要为框架所消耗的性能付出成本,并且在应用程序首次加载时没有额外损失。

下载量

从下图可以看到目前vue的下载量存在量级的差别

image-20230904105900536-3796345-3816084.png

image-20230904110012023-3816102.png

Svelte比Vue快和小

  • Vue

image-20230904133034678-3816189.png

image-20230904133115607-3805476-3805479-3805479-3816196.png

  • Svelte

image-20230912115515891

image-20230912115515891.png

image-20230912115623469.png

UI框架:

  1. Vue框架的UI框架有许多选择,以下是一些常见的UI框架:
  1. Element UI:一款由饿了么团队开发的成熟的UI框架。
  2. Vuetify:一个基于Material Design的Vue组件库。
  3. Ant Design Vue:一个基于Ant Design设计语言的Vue组件库。
  4. BootstrapVue:是对Bootstrap框架的Vue封装,提供了一套响应式的UI组件。
  5. Buefy:一个基于Bulma CSS框架和Vue的UI组件库。
  6. Quasar:一个全能的Vue框架,包含了一套完整的UI组件和工具集。
  7. Muse-UI:一个优雅的、基于Material Design的Vue UI框架。
  8. iView:一套基于Vue的高质量UI组件库。
  9. Tailwind CSS:一个高度可定制的CSS框架,可以与Vue无缝整合。
  1. Svelte框架相比Vue框架而言,由于其独特的编译方式,UI框架的选择相对较少。以下是一些适用于Svelte框架的UI框架:
  1. Carbon:由IBM开发的一套通用UI组件库,提供了一系列现代化的UI组件和样式。
  2. Sveltestrap:一个基于Bootstrap的Svelte组件库,提供了一套简洁而强大的UI组件。
  3. Smelte:一个基于Material Design的Svelte组件库,提供了一套漂亮的UI组件和样式。
  4. Svelte Material UI:一个致力于将Material Design引入到Svelte中的UI组件库。
  5. Svelteify:一个基于Tailwind CSS的Svelte组件库,提供了一套响应式的UI组件和样式。

开始

1. 文件名

  • Vue

文件以后缀.vue命名

  • Svelte

文件以后缀.svelte命名

2. 语法格式

  • Vue
<script setup>
操作...
</script><template>
视图...
</template><style>
全局...
</style><style scope>
局部...
</style>
  • Svelte

默认style只影响当前组件;

需要设置全局css可以用:global(...)标记

<script>
操作...
</script>
​
视图...
​
<style>
局部...
  p {
    color: #1893fe;
  }
  
  /* 全局 */
  div :global(strong) {
    /* 这里表示全局应用于被<div>包裹的<strong>标签 */
    color: goldenrod;
  }
</style>
​
​

3. 组件

  • Vue
  1. 声明 一个Component.vue的文件,在里面处理视图和逻辑
  2. 引入
<script setup>
  import Component from './Component.vue'
</script><template>
  <Component></Component>
</template>
  • Svelte
  1. 声明 一个Component.Svelte的文件,在里面处理视图和逻辑
  2. 引入
<script>
  import Component from './Component.Svelte'
</script><Component></Component>

4. HTML标记

  • Vue
<script setup>
  const string = `here's some <strong>HTML!!!</strong>`;
</script><!-- 使用v-html标记 -->
<p v-html="string"></p>
  • Svelte
<script>
  let string = `here's some <strong>HTML!!!</strong>`;
</script><!-- 使用@html标记 -->
<p>{@html string}</p>

5. 响应式数据

  • Vue
<script setup>
  import {ref, reactive} from 'vue'
  const count = ref(0)
  const handleClick = () => {
    count.value += 1
  }
</script>
<template>
​
{{ count }}
​
<button @click="handleClick">
  点我  {{ count }}
  </button>
</template>
  • Svelte
<script>
  let count = 0;
​
  function handleClick() {
    count += 1;
  }
</script>
​
<button on:click={handleClick}>
  Clicked {count}
  {count === 1 ? 'time' : 'times'}
</button>

6. 计算属性

  • Vue

引入computed 进行计算

<script setup>
  import { computed, ref } from 'vue'
  const count = ref(1)
  const doubleCount = computed(() => {
    return count.value * 2
  })
  const handleClick = () => {
    count.value += 1
  }
</script>

<template>
  <button @click="handleClick">点我{{ count }}</button>
  结果:{{ doubleCount }}
</template>
  • Svelte

引入 $:进行计算

<script>
  let count = 1
  $: doubleCount = count * 2
  function handleClick() {
    count += 1
  }
</script>

<button on:click={handleClick}>点我{count}</button>
count * 2 = {doubleCount}

7. 动态数据监听

  • Vue
<script setup>
  import { watch, ref } from 'vue'

  let count = ref(0)
  watch(count, () => {
    if (count.value > 9) {
      count.value = 9
      alert('count is dangerously high!')
    }
  })
  function handleCick() {
    count.value += 1
  }
</script>

<template>
  <button @click="handleCick">点我 {{ count }}</button
  >{{ count >= 9 ? 'count is dangerously high!' : count }}
</template>
  • Svelte
<script>
  let count = 0

  $: if (count > 9) {
    count = 9
    alert(`count is dangerously high!`)
  }

  function handleCick() {
    count += 1;
  }
</script>

<button on:click={handleCick}>点我 {count}</button> {count >= 9 ? "count is dangerously high!" : count}

8. Props 外部传入的值

  • Vue
  1. 使用 defineProps()标记外部传入的属性名;
  2. 在组件中使用:属性名="值"赋值;
  3. 也可以默认当前属性的值;
// Compontent 组件声明属性
//  defineProps(['receiveCount'])

  defineProps({
    receiveCount: Number,
    receiveStr: {
      type: String,
      default: 'is a string',
    },
  })

// App组件中引入 Compontent组件
<Compontent :receiveCount="99" ></Compontent>
  • Svelte
  1. 使用export let name标记外部传入的属性名;
  2. 在组件中使用对象名={值}赋值;
  3. 可以默认当前属性的值;
// Compontent 组件声明属性
  export let receiveCount;
  export let receiveStr = 'is a string'

// App组件中引入 Compontent组件
<Compontent receiveCount={99}></Compontent>

9. If语句

  • Vue

在标签中使用 v-if="条件1"决定当前内容是否显示,可以直接接着写入v-else-if=" 条件2"或者v-else来判断显示其他内容

<div v-if="count < 3">我是count小于3的时候显示的</div>
  <div v-else-if="count >= 3 && count <= 6">
    我是count 大于等于 3 并且 小于等于 6的时候显示的
  </div>
  <div v-else>我是不符合的时候显示的</div>
  • Svelte
  1. 首先使用{#if 条件1}标记判断开始,使用{/if }标记判断结束;在标记中间显示内容
  2. 可以在{#if 条件1}{/if}中间可以使用 {:else }或者{:else if 条件3}来依次判断内容显示
{#if count < 3}
    <p>我是count小于3的时候显示的</p>
{:else if count >= 3 && count <= 6} 
    <p>我是count 大于等于 3 并且 小于等于 6的时候显示的</p>
{:else}
    <p>我是不符合的时候显示的</p>
{/if}

10 循环

  • Vue
  1. 在被循环的节点上使用 v-for="元素对象 in 元素数组"
  2. 循环创建的节点是需要添加唯一标志的,这里的唯一标志是通过在节点上添加:key="唯一标志"来标记的
<script setup>
 let cats = [
    { id: 'J---aiyznGQ', name: 'Keyboard Cat' },
    { id: 'z_AbfPXTKms', name: 'Maru' },
    { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' },
  ]
</script>

<template>

    <ul>
        <li v-for="{id, name} in cats" :key="id">
            {{ name }}的id是:{{ id }}
        </li>
    </ul>
</template>
  • Svelte
  1. 在要循环的节点外围使用{#each 元素数组 as 元素 (唯一标识)} {/each}标记包含住,然后在标记内写入要循环的节点
  2. 这里注意Each循环需要添加一个唯一标志,这里的唯一标志是在 {#each 元素数组 as 元素 (唯一标识)}标记的开始处使用
<script>
  let cats = [
    { id: 'J---aiyznGQ', name: 'Keyboard Cat' },
    { id: 'z_AbfPXTKms', name: 'Maru' },
    { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' },
  ]
</script>

<ul>
  {#each cats as { id, name } (id)}
    <li>
      {name}的id是:{id}
    </li>
  {/each}
</ul>

11. #await的不同状态

  • Vue

v-loading=loading

  • Svelte

#await可以监听到一个异步的变量状态:pending、fulfilled和rejected;

#await有多种使用

1. 监听pending、fulfilled、rejected三种状态
{#await expression}...{:then name}...{:catch name}...{/await}
2. 监听pending、fulfilled
{#await expression}loading...{:then name}...{/await}
3. 监听fulfilled
{#await expression then name}...{/await}
4. 监听rejected
{#await expression catch name}...{/await}
<script>
  let request = loadData()

  /**
   * 开始网络请求
   */
  async function loadData() {
    const res = await sleep()
    return res
  }
  /**
   * 模拟网络请求
   */
  function sleep() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const count = Math.ceil(Math.random() * 10)
        count % 2 ? resolve(count) : reject('error')
      }, 2e3)
    })
  }

  function handleClick() {
    request = loadData()
  }
</script>

<button on:click={handleClick}>开始网络请求</button>
{#await request}
  <p>loading....</p>
{:then name}
  <p>value的值是:{name}</p>
{:catch}
  <p>哎呀出错了</p>
{/await}

12. Event

  • Vue
  1. 使用v-on:事件名@事件名都可以给标签绑定事件
  2. 在事件名后添加.事件限制,@click.once
<script setup>
  import { reactive } from 'vue'
  const m = reactive({
    x: 0,
    y: 0,
  })
  function handleMove(event) {
    m.x = event.clientX
    m.y = event.clientY
  }
  function handleClick() {
    alert('only can click once!')
  }
</script>

<template>
  <div class="box" v-on:mousemove="handleMove" @click.once="handleClick">
    The mouse position is {{ m.x }} x {{ m.y }}
  </div>
</template>

<style scoped>
  .box {
    width: 100%;
    height: 100px;
    border: 1px solid #ccc;
  }
</style>
  • Svelte
  1. 使用on:事件名给标签绑定事件
  2. 在事件名后添加|事件限制,on:click|once
<script>
  let m = {
    x: 0,
    y: 0,
  }
  function handleMove(event) {
    m.x = event.clientX
    m.y = event.clientY
  }
  function handleClick() {
    alert('only can click once!')
  }
</script>

<div class="box" on:mousemove={handleMove} on:click|once={handleClick}>
  The mouse position is {m.x} x {m.y}
</div>

<style>
  .box {
    width: 100%;
    height: 100px;
    border: 1px solid #ccc;
  }
</style>

12.1 子向父传递数据

  • Vue
  1. 子组件,创建和分发事件

使用defineEmits注册事件,在特定情况下触发事件传递数据

<script setup>
  const emits = defineEmits(['message'])

  function handleMessageBtn() {

    emits('message', { text: 'hello, i am message' })
  }
</script>
<template>
  <button @click="handleMessageBtn">message btn</button>
</template>
  1. 父组件接收数据和响应事件

在添加子组件的地方使用@事件名=响应事件

<script setup>
  import { reactive, ref } from 'vue'
  import EventChildComponent from './EventChildComponent.vue'
  const message = ref('')
  function handleMessageClick(value) {
    message.value = value?.text
    alert(message.value)
  }
</script>
<template>
  <EventChildComponent @message="handleMessageClick"></EventChildComponent>
</template>
  • Svelte
  1. 子组件

使用createEventDispatcher创建事件分发器,在特定的情况下触发事件传递数据

<script>
  import { createEventDispatcher } from 'svelte'

  // 创建事件分发器
  const dispatch = createEventDispatcher()

  function handleMessageBtn() {
    dispatch('message', {
      text: 'hello, i am message',
    })
  }
</script>

<button on:click={handleMessageBtn}>message btn</button>
  1. 父组件

在添加子组件的地方使用on:事件名={响应事件}

<script>
  import EventChildComponent from './EventChildComponent.svelte'
  let message = ''
  function handleMessageClick(event) {
    console.log(event)
    // event.detail 表示分发出来的数据
    message = event?.detail?.text
    alert(message)
  }
</script>
<EventChildComponent on:message={handleMessageClick} />

12.2 孙子组件向上传递数据

  • Vue

使用provide为后代提供一个key和值,值可以是变量、常量或回调事件;

使用inject接收祖先组件注入的key和值,并返回一个对象

  1. 祖先组件
<script setup>
	 import { provide } from 'vue'
  
    provide('gradesun-message', (val) => {
      alert(val)
    })
 </script>
  1. 后代组件
<script setup>
  import { inject } from 'vue'
  const gradesunFunc = inject('gradesun-message')

  function handleClickCC() {
    gradesunFunc('我是孙子组件')
  }
</script>
<template>
  <button @click="handleClickCC">gradesun btn</button>
</template>
  • Svelte

使用 getContextsetContext设置和获取上下文来进行跨组件通信

  1. 祖先组件
<script>
  import { setContext } from 'svelte'
  import EventChildComponent from './EventChildComponent.svelte'
  
  setContext('p-message', '我是老祖')
  setContext('p-fun',(obj) => {
    console.log(obj)
  })
  
</script>

<EventChildComponent  />
  1. 子组件组件
<script>
  import { getContext } from 'svelte'
	const pMsg = getContext('p-message')
  const pFun = getContext('p-fun')
  
</script>
{pMsg}
<button on:click={() => pFun('123')}>
gradesun btn
</button>

12.3 自定义事件,并在外部实现

  • Vue

使用defineEmits()声明事件名,在特定情况下响应

<script setup>
  // 声明
  const emits = defineEmits(['message'])
  
  function handleClick () {
    // 响应
    emits('message')
  }
</script>

<template>
<button @click="handleClick">
  click
  </button>
</template>
  • Svelte

需要使用on:事件名进行标记

  1. 子组件
<button on:click>click me</button>
  1. 父组件
<script>
    import DomEventForwardingComponent from '../components/DomEventForwardingComponent.svelte'

  
function handleClickDomEvent () {
    console.log(123);
  }
</script>

      <DomEventForwardingComponent on:click={handleClickDomEvent}></DomEventForwardingComponent>

13. Bindings

数据双向绑定实现了视图影响数据,数据也影响视图。

  • Vue

实现双向绑定简单的说就是使用v-on监听变化,v-bind绑定数据。

提供了一个语法糖:v-model

<script setup>
import { ref } from 'vue'
const name = ref('')
const scoops = ref(1)
</script>

<template>
  <input v-model="name" type="text" placeholder="请输入内容" />
  <p>Hello {{ name || 'Vue' }}!</p>

  <!-- 多选 -->
  <label>
    <input type="radio" :value="1" />
    One scoop
  </label>

  <label>
    <input type="radio" v-model="scoops" :value="2" />
    Two scoops
  </label>
  <p>scoops 的值是: {{ scoops }}</p>
</template>
  • Svelte

它的元素指令有很多,参考Element directives

<script>
  let name = ''
  let scoops = 1
</script>

<input bind:value={name} type="text" placeholder="请输入内容" />
<p>Hello {name || 'Svelte'}!</p>
<!-- 多选 bind:group -->
<label>
  <input type="radio" bind:group={scoops} value={1} />
  One scoop
</label>

<label>
  <input type="radio" bind:group={scoops} value={2} />
  Two scoops
</label>
<p>scoops 的值是: {scoops}</p>

13.1 bind:this == ?

  • Vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const canvas = ref(null)
const frame = ref(null)
onMounted(() => {
  const ctx = canvas.value.getContext('2d')

  ;(function loop() {
    console.log(1)
    frame.value = requestAnimationFrame(loop)
    const imageData = ctx.getImageData(
      0,
      0,
      canvas.value.width,
      canvas.value.height
    )

    for (let p = 0; p < imageData.data.length; p += 4) {
      const i = p / 4
      const x = i % canvas.value.width
      const y = (i / canvas.value.height) >>> 0

      const t = window.performance.now()

      const r = 64 + (128 * x) / canvas.value.width + 64 * Math.sin(t / 1000)
      const g = 64 + (128 * y) / canvas.value.height + 64 * Math.cos(t / 1400)
      const b = 128

      imageData.data[p + 0] = r
      imageData.data[p + 1] = g
      imageData.data[p + 2] = b
      imageData.data[p + 3] = 255
    }

    ctx.putImageData(imageData, 0, 0)
  })()
})

onUnmounted(() => {
  console.log(2)
  cancelAnimationFrame(frame.value)
})
</script>

<template>
  <canvas ref="canvas"></canvas>
</template>

<style scoped>
canvas {
  width: 100%;
  height: 100%;
  background-color: #666;
  -webkit-mask: url(/src/assets/svelte-logo-mask.svg) 50% 50% no-repeat;
  mask: url(/src/assets/svelte-logo-mask.svg) 50% 50% no-repeat;
}
</style>
  • Svelte
<script>
  import { onMount } from 'svelte'

  let canvas

  onMount(() => {
    console.log(canvas)
    const ctx = canvas.getContext('2d')
    let frame
    ;(function loop() {
      frame = requestAnimationFrame(loop)

      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)

      for (let p = 0; p < imageData.data.length; p += 4) {
        const i = p / 4
        const x = i % canvas.width
        const y = (i / canvas.height) >>> 0

        const t = window.performance.now()

        const r = 64 + (128 * x) / canvas.width + 64 * Math.sin(t / 1000)
        const g = 64 + (128 * y) / canvas.height + 64 * Math.cos(t / 1400)
        const b = 128

        imageData.data[p + 0] = r
        imageData.data[p + 1] = g
        imageData.data[p + 2] = b
        imageData.data[p + 3] = 255
      }

      ctx.putImageData(imageData, 0, 0)
    })()

    return () => {
      cancelAnimationFrame(frame)
    }
  })
</script>

dom
<canvas bind:this={canvas} />

<style>
  canvas {
    width: 100%;
    height: 100%;
    background-color: #666;
    -webkit-mask: url(/src/lib/images/svelte-logo-mask.svg) 50% 50% no-repeat;
    mask: url(/src/lib/images/svelte-logo-mask.svg) 50% 50% no-repeat;
  }
</style>

13.2 Component !

  • Vue
  1. 子组件Keypad
<script setup>
import { defineExpose, ref } from 'vue'

const emits = defineEmits(['submit'])

const password = ref('')
const select = (val) => {
  password.value += val
}
const handleClear = () => {
  password.value = ''
}
const handleSubmit = () => {
  emits('submit')
}

defineExpose({
  password,
})
</script>

<template>
  <div class="keypad">
    <button @click="select(1)">1</button>
    <button @click="select(2)">2</button>
    <button @click="select(3)">3</button>
    <button @click="select(4)">4</button>
    <button @click="select(5)">5</button>
    <button @click="select(6)">6</button>
    <button @click="select(7)">7</button>
    <button @click="select(8)">8</button>
    <button @click="select(9)">9</button>

    <button :disabled="!password" @click="handleClear">clear</button>
    <button @click="select(0)">0</button>
    <button :disabled="!password" @click="handleSubmit">submit</button>
  </div>
</template>

<style scoped>
.keypad {
  display: grid;
  grid-template-columns: repeat(3, 5em);
  grid-template-rows: repeat(4, 3em);
  grid-gap: 0.5em;
}

button {
  margin: 0;
}
</style>
  1. 父组件ComponentComponent
<script setup>
import { ref, watch } from 'vue'
import Keypad from './Keypad.vue'

const keypad = ref(null)
const pin = ref('')

watch(
  () => keypad.value?.password,
  (newVal) => {
    pin.value = newVal ? newVal.replace(/\d(?!$)/g, '•') : 'enter your pin'
  },
  { immediate: true, deep: true }
)

function handleSubmit() {
  alert(`submitted ${keypad.value?.password}`)
}
</script>
<template>
  <h1 :class="{ pin: keypad?.password }">{{ pin }}</h1>
  <Keypad ref="keypad" @submit="handleSubmit"></Keypad>
</template>

<style>
h1 {
  color: #ccc;
}
h1.pin {
  color: #333;
}
:global(body.dark) h1 {
  color: #444;
}
:global(body.dark) h1.pin {
  color: #fff;
}
</style>
  • Svelte
  1. 父组件ComponentComponent
<script>
  import Keypad from './Keypad.svelte'

  let pin

  $: view = pin ? pin.replace(/\d(?!$)/g, '•') : 'enter you pin'

  function handleSubmit() {
    alert(pin)
  }
</script>

<h1 class:pin>{view}</h1>
<Keypad bind:value={pin} on:submit={handleSubmit} />

<style>
  h1 {
    color: #ccc;
  }
  h1.pin {
    color: #333;
  }
  :global(body.dark) h1 {
    color: #444;
  }
  :global(body.dark) h1.pin {
    color: #fff;
  }
</style>
  1. 子组件Keypad

<script>
  import { createEventDispatcher } from 'svelte'

  export let value = ''

  const dispatch = createEventDispatcher()

  const select = (num) => () => (value += num)
  const clear = () => (value = '')
  const submit = () => dispatch('submit')
</script>

<div class="keypad">
  <button on:click={select(1)}>1</button>
  <button on:click={select(2)}>2</button>
  <button on:click={select(3)}>3</button>
  <button on:click={select(4)}>4</button>
  <button on:click={select(5)}>5</button>
  <button on:click={select(6)}>6</button>
  <button on:click={select(7)}>7</button>
  <button on:click={select(8)}>8</button>
  <button on:click={select(9)}>9</button>

  <button disabled={!value} on:click={clear}>clear</button>
  <button on:click={select(0)}>0</button>
  <button disabled={!value} on:click={submit}>submit</button>
</div>

<style>
  .keypad {
    display: grid;
    grid-template-columns: repeat(3, 5em);
    grid-template-rows: repeat(4, 3em);
    grid-gap: 0.5em;
  }

  button {
    margin: 0;
  }
</style>

14 tick

一个可以立刻更新DOM的方法

  • Vue
<script setup>
import { ref, nextTick } from 'vue'

const text = ref('Select some text and hit the tab key to toggle uppercase')

function handleKeyDown(event) {
  if (event?.key !== 'Tab') return

  if (event.target) {
    const { selectionStart, selectionEnd, value } = event.target
    const selection = value.slice(selectionStart, selectionEnd)

    const replacement = /[a-z]/.test(selection)
      ? selection.toUpperCase()
      : selection.toLowerCase()

    text.value =
      value.slice(0, selectionStart) + replacement + value.slice(selectionEnd)

    nextTick(() => {
      event.target.selectionStart = selectionStart
      event.target.selectionEnd = selectionEnd
    })
  }
}
</script>

<template>
  <textarea v-model="text" @keydown.prevent="handleKeyDown"></textarea>
</template>

<style scoped>
textarea {
  width: 100%;
  height: 200px;
}
</style>
  • Svelete
<script>
  import { tick } from 'svelte'
  let text = 'Select some text and hit the tab key to toggle uppercase'

  function handleKeyDown(event) {
    if (event?.key !== 'Tab') return

    if (event.target) {
      const { selectionStart, selectionEnd, value } = event.target
      const selection = value.slice(selectionStart, selectionEnd)

      const replacement = /[a-z]/.test(selection)
        ? selection.toUpperCase()
        : selection.toLowerCase()

      text =
        value.slice(0, selectionStart) + replacement + value.slice(selectionEnd)

  tick().then(() => {
        event.target.selectionStart = selectionStart
        event.target.selectionEnd = selectionEnd
      })
    }
  }
</script>

<textarea bind:value={text} on:keydown|preventDefault={handleKeyDown} />

<style>
  textarea {
    width: 100%;
    height: 200px;
  }
</style>

15 stores

  • Vue
  1. 下载 npm install pinia
  2. 在入口函数处创建
import { createPinia } from 'pinia'

app.use(createPinia())
  1. 创建仓库userInfo,注意导入并使用defineStore
import { defineStore } from 'pinia'

export const useUserInfo = defineStore('userInfoStore', {
  state () {
    return {
      info: null
    }
  },
  getters: {
    // 获取id
    userId (state) {
      return state?.id
    },
    // 获取用户名
    userName (state) {
      return state?.name
    }
  },
  actions: {
    /**
     * 设置用户数据
     * @param {Object} obj 用户数据
     */
    setInfo (obj) {
      obj && (this.info = obj)
    },
    /**
     * 设置用户数据
     * @param {string} key 键名
     * @param {any} value 数据
     */
    changeInfo (key, value) {
      key && value && (this.info[key] = value)
    }
  }
})
  1. 在组件中使用
<script setup>
import { useUserInfo } from '../stores/index.store'

const userInfo = useUserInfo()

function handleSetInfo() {
  // userInfo.info = {
  //   id: 1,
  //   gender: '男',
  //   name: 'AI',
  //   age: 18,
  //   info: '吊炸天',
  // }
  userInfo.setInfo({
    id: 1,
    gender: '男',
    name: 'AI',
    age: 18,
    info: '吊炸天',
  })
}

function handleChangeInfo() {
  // userInfo.info['name'] = 'AI'
  userInfo.changeInfo('name', 'AI')
}
</script>

<template>
  <ul>
    <template v-if="userInfo.info">
      <li v-for="key in Object.keys(userInfo.info)" :key="key">
        {{ key }}: {{ userInfo.info[key] }}
      </li>
    </template>
  </ul>
  <button @click="handleSetInfo">setInfo</button>
  <button @click="handleChangeInfo">changeInfo</button>
</template>
  • Svelte

writable, readable, get, derived

  1. 创建仓库,引入svelte/store并使用writable
import { writable } from 'svelte/store'

/**
 * 创建用户资料仓库
 */
function createUserInfo () {
  const { subscribe, set, update } = writable(null)

  /**
   * 赋值用户资料
   * @param {Object} obj 对象
   */
  function setInfo (obj) {
    obj && set(obj)
  }

  /**
   * 更新用户资料
   * @param {string} key 更新的字段名
   * @param {any} value 更新的值
   */
  function updateInfo (key, value) {
    update((info) => {
      info && info.hasOwnProperty([key]) && value && (info[key] = value)
      return info
    })
  }
  return { subscribe, setInfo, updateInfo }
}
export const userInfo = createUserInfo()
  1. 在组件中使用
<script>
  import { userInfo } from '@/store/index.Store.js'

  // 用户资料
  function handleSetInfo() {
    userInfo.setInfo({
      id: 1,
      gender: '男',
      name: 'shutong',
      age: 18,
      info: '帅哥',
    })
  }
  function handleUpdateInfo() {
    userInfo.updateInfo('age', 20)
  }
</script>


<!-- 用户资料 -->
<div>
  <div>
    个人资料:
    <ul>
      {#if $userInfo}
        {#each Object.keys($userInfo) as key (key)}
          <li>{key} : {$userInfo[key]}</li>
        {/each}
      {/if}
    </ul>
  </div>
  <button on:click={handleSetInfo}>设置用户资料</button>
  <button on:click={handleUpdateInfo}>更新</button>
</div>

16 slot

  • Vue
  1. Widget子组件
<script setup>
const props = defineProps({
  goodsList: {
    type: Array,
  },
})
</script>

<template>
  <header>
    <slot name="header"> 标题 </slot>
    <div><slot name="description"> 描述 </slot></div>
  </header>
  <main>
    <ul>
      <li v-for="(goods, key) in props?.goodsList" :key="key">
        <slot name="default" :g="goods" :key="key">{{ goods }}</slot>
      </li>
    </ul>
  </main>

  <footer>
    <slot name="footer"> 浏览量:100 </slot>
  </footer>
</template>

<style scoped>
footer {
  font-size: 12px;
}
</style>
  1. SlotComponent父组件
<script setup>
import Widget from './Widget.vue'
import { ref } from 'vue'
const items = ref(['🍎', '🍌', '🍐'])

</script>

<template>
  <Widget :goods-list="items">
    <template #header> <h1>水果店</h1> </template>
    <template #description>想吃新鲜水果的快点来呀</template>
    <template #default="slotProps">
      <span>下标 {{ slotProps.key }}</span>
      <span>名称 {{ slotProps.g }}</span>
    </template>
  </Widget>
</template>
  • Svelte
  1. Widget子组件
<script>
  export let items
</script>

<div>
  <slot name="header">its header default text</slot>
  <slot>its content default text</slot>
  <!-- $$slots获取父级传递过来的参数 -->
  {#if $$slots.description}
    <slot name="description" />
  {/if}

  <!-- goods -->
  <ul>
    {#each items as item (item)}
      <slot name='adb' {item} />
    {/each}
  </ul>

  <slot name="footer">its footer default text</slot>
</div>
  1. SlotWidget父组件
<script>
  import SlotWidget from './SlotWidget.svelte'

  let items = ['🍎', '🍌', '🍐']
</script>

<SlotWidget {items} let:item>
  <div slot="header">世界你好</div>
  <div slot="description">我是描述文本</div>
  <div slot="default">名称{item}</div>
  <svelte:fragment slot="footer">
    <p>All rights reserved.</p>
    <p>Copyright (c) 2019 Svelte Industries</p>
  </svelte:fragment>
</SlotWidget>

17 声明周期

  • Vue

    1. beforeCreate:在实例初始化之后,数据观测(data observer)和事件/监听事件配置之前被调用。
    2. created:实例创建完成后调用,此阶段完成了数据观测,属性和方法的运算,以及事件监听,$el属性还没有显示出来。
    3. beforeMount:在挂载开始之前被调用,相关的render函数首次被调用。这个时候还没有开始挂载节点,$el属性目标不会有任何变化。
    4. mounted:el被新创建的vm.��替换,并挂载到实例上去之后调用该钩子。如果实例被挂载到一个文档内元素上,当�������被调用时��.e**l替换,并挂载到实例上去之后调用该钩子。如果实例被挂载到一个文档内元素上,当mounted被调用时v**m.el也在文档内。
    5. beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前。
    6. updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。
    7. beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
    8. destroyed:Vue实例销毁后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。
  • Svelte

    1. beforeUpdate :Schedules a callback to run immediately before the component is updated after any state change
    2. onMount: The onMount function schedules a callback to run as soon as the component has been mounted to the DOM.
    3. afterUpdate:Schedules a callback to run immediately after the component has been updated
    4. onDestroy:Schedules a callback to run immediately before the component is unmounted.