Component 鬼牌(一): 看 props 决定 Component

鬼牌,在此借用的意思是「可以成为任何一张牌」

Dynamic Components 可以当鬼牌

Dynamic Components 技术使用 <component :is=""> 让 component 可以抽换成其它已经规画好的 component。

需求: 长型表单

不管是申请信用卡、成为购物网站会员、基金申购平台帐号,可能就有机会填写到这麽长的表单,设计师为了让使用者的体验较好,所以会把长型表单改成多个短表单,一步一步的引导使用者将表单填写完毕。

在这个过程,如果需要将资料暂存在 localStorage 中,就变成是一件麻烦的事情,是否要一段一段的将资料储存在各别的 localStorage 中?还是有其它的办法呢?

以下图为例,拿四个栏位的表单当作长型表单。(数量请大家自行指数成长)

通常都是分成第一步、第二步....这样。

原本,如果是很长的表单,我们会这样写。对工程师来说方便,data 也只有一个要储存在 localStorage 也只有一个栏位就好。

App.vue

<template>
  <div id="app">
    <form @submit.prevent="onSubmit">
      <div>
        <label for="field1">
          field1
          <input type="text" id="field1"
          	  v-model="data.field1">
        </label>
      </div>
      <div>
        <label for="field2">
          field2
          <input type="text" id="field2"
          	  v-model="data.field2">
        </label>
      </div>
      <div>
        <label for="field3">
          field3
          <input type="text" id="field3"
          	  v-model="data.field3">
        </label>
      </div>
      <div>
        <label for="field4">
          field4
          <input type="text" id="field4"
          	  v-model="data.field4">
        </label>
      </div>
      <input type="submit">
    </form>
  </div>
</template>
export default {
  name: 'App',
  methods: {
    onSubmit() {
      console.log('submit', JSON.stringify(this.mock));
    },
  },
  data() {
    return {
      index: 1,
      mock: {
        field1: 'field_data1',
        field2: 'field_data2',
        field3: 'field_data3',
        field4: 'field_data4',
      },
    };
  },
};

有几步就拆成几个 component

直接把表单拆成两个 component。
并且将栏位拆进 component 里面 (因为接下来要抽换它们)

<template>
  <div id="app">
    <form @submit.prevent="onSubmit">
      <step1
        :data="mock"
        @update:data="mock=$event"
      ></step1>
      <step2
        :data="mock"
        @update:data="mock=$event"
      ></step2>
      <input type="submit">
    </form>
  </div>
</template>

注册 2 个 component

import step1 from '@/components/LongForm/step1.vue';
import step2 from '@/components/LongForm/step2.vue';

export default {
  name: 'App',
  // ...
  components: {
    step1,
    step2,
  },
  // ...
}

step1.vue

因为两步目前长得一样,所以只看其中一个就好了

<template>
  <div>
    <div>
      <label for="field1">
        field1
        <input type="text"
          id="field1"
          :value="data.field1"
          @input="$emit('update:data', {
            ...data,
            field1: $event.target.value
          })">
      </label>
    </div>
    <div>
      <label for="field2">
        field2
        <input type="text"
          id="field2"
          :value="data.field2"
          @input="$emit('update:data', {
            ...data,
            field2: $event.target.value
          })">
      </label>
    </div>
  </div>
</template>

在此,我们已经把 v-model 拆开写,「单纯只有栏位 binding 的 code 」只会出现在 html 上面。

在此只是一个简单的 pure component 如果要加上复杂的逻辑,就有相当乾净的环境可以加上去。

export default {
  name: 'LongForm1',
  props: {
    data: {
      type: Object,
      requried: true,
    },
  }
};

换成多个 <component :is="">

并且将 :is binding 成 step1 和 setp2 的 component 名字。

is, API — Vue.js

<template>
  <div id="app">
    <form @submit.prevent="onSubmit">
      <component
        :is="'step1'"
        :data="mock"
        @update:data="mock=$event"
      ></component>
      <component
        :is="'step2'"
        :data="mock"
        @update:data="mock=$event"
      ></component>
      <input type="submit">
    </form>
  </div>
</template>

换成一个 <component :is="">

换成同一个 <component :is=""> 时,可以注意两个 component 的 v-bind 和 v-on 必须要一样。

<template>
  <div id="app">
    <div>现在是第 {{ current_step }} 步</div>
    <form @submit.prevent="onSubmit">
      <component
        :is="`step${index}`"
        :data="mock"
        @update:data="mock=$event"
      ></component>
      <input v-if="current_step === 2" type="submit">
      <input v-else type="button" value="下一步" @click="onNext">
    </form>
  </div>
</template>
export default {
  name: 'App',
  // ...
  data() {
    return {
      current_step: 1,
      // ...
    }
  },
  methods: {
    onNext() {
      this.current_step += 1;
      console.log('current_step', this.current_step);
    },
    onSubmit() {
      console.log('submit', JSON.stringify(this.mock));
    },
  },
},

最後的画面

一开始是这样

第一步

第二步
写到第二步,如果重新整理,会回到第一步。

最後提交表单。

存到 localStorage

在 onNext 的时候,就可以储存起来。
而且,因为是长表单,所以当下到第几步和资料都给它储存下来。

修改的程序
在 created 要把 localStorage 读取出来恢复资料。
在 onNext 的地方要在 localStorage 储存现况。

export default {
  name: 'App',
  // ...
  created() {
    const demoStatus = JSON.parse(localStorage.getItem('DemoMock'));
    if (demoStatus.data) {
      this.mock = demoStatus.data;
    }
  },
  // ...
  methods: {
    onNext() {
      this.current_step += 1;
      console.log('current_step', this.current_step);
      localStorage.setItem('DemoMock', JSON.stringify({
        setp: this.current_step,
        data: this.mock
      }));
    },
  }
}

<<:  Day 10:快速排序(quicksort)

>>:  30天轻松学会unity自制游戏-捡道具

【Day8】在本地简单的建立 django(Django API Server的架设 1/3)

基本上目前看到的方法大致上都如下图所示,所以我目前实作的也是如此, 因为脑子里没有更好的解决方案(在...

学会网页制作除了javascript还要会....HTML1

从基本的 HTML、CSS 网页设计开始入门,先学习静态版面设计技巧,再带入动态的 JavaScri...

【Day04-档案】你知道Excel最大可以开多少笔资料吗?

前一天我们介绍了用来资料处理最基本的pandas套件 那今天我们则是来谈一下不同的档案类型 我们都知...

Day 12 - Semigroup I

Definition of a Semigroup 一个集合(Set)或称型别(Type) 有 co...

[Day10] 文本/词表示方式(一)-前言

一. 前言 在如今社群网路蓬勃的时代,从网路充斥着许多文字资料,要如何有效的分析文字让电脑可以知道我...