Vue

Vue3 + TypeScript + Vuex4 初尝试

Posted by cody1991 on March 20, 2021

一个简单的任务列表构建,本文的代码在 vue3-typescript 可自行下载

准备工作

使用 Vue CLI 脚手架工具快速创建项目

1
2
3
sudo yarn global add @vue/cli

vue create vue3-ts

手动选择我们想要的配置

1
2
3
4
5
6
7
8
9
10
Vue CLI v4.5.12
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, TS, Vuex, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Pick a linter / formatter config: Basic
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

之后进入到我们的 vue3-ts 文件夹运行 yarn serve 就可以启动项目了,看到我们熟悉的界面

接着安装我们的 UI 库,并不想自己写样式。。

1
yarn add bulma

调整下 App.vue 文件,引入 bulma

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
  <div>App</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'App',
});
</script>

<style>
@import '~bulma/css/bulma.css';
</style>

需求明确

然后明确下我们这个项目的任务,其实就是增删改查任务,CURD boy~

Vuex

Vuex 里面的四要素: Store Getters Actions Mutations

States 状态都存储在 Store 里面,我们只能同步通过 Mutations 去修改 States,异步修改 States 的话可以使用 Actions 在回调中调用 Mutations 修改 StatesGetters 是方便获取 States 的一种手段,类似于 Computed

开始构建我们的 Store 吧~

States

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// store/state.ts
export type TaskItem = {
  id: number;
  title: string;
  description: string;
  createdBy: string;
  assignedTo: string;
  completed: boolean;
  editing: boolean;
};

export type State = {
  loading: boolean;
  tasks: TaskItem[];
  showCreateModal: boolean;
  showEditModal: boolean;
  editModalTaskId: number | undefined;
  showTaskModal: boolean;
  showTaskId: number | undefined;
};

export const state: State = {
  loading: false,
  tasks: [],
  showCreateModal: false,
  showEditModal: false,
  editModalTaskId: undefined,
  showTaskModal: false,
  showTaskId: undefined,
};

之后引入就好了

1
2
3
4
5
6
7
8
import { createStore } from 'vuex';
import { state } from './state';
export default createStore({
  state,
  mutations: {},
  actions: {},
  modules: {},
});

Mutations

之后编写下我们可以修改状态的方法。

  1. 编写我们需要修改状态的方法枚举值
  2. 编写 Mutations 相关方法的类型定义
  3. 实现 Mutations 相关方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { MutationTree } from 'vuex';

import { State, TaskItem } from './state';

export enum MutationType {
  CreateTask = 'CREATE_TASK',
  SetTasks = 'SET_TASKS',
  CompleteTask = 'COMPLETE_TASK',
  RemoveTask = 'REMOVE_TASK',
  EditTask = 'EDIT_TASK',
  UpdateTask = `UPDATE_TASK`,
  SetLoading = 'SET_LOADING',
  SetCreateModal = 'SET_CREATE_MODAL',
  SetEditModal = 'SET_EDIT_MODAL',
  SetTaskModal = 'SET_TASK_MODAL',
}

export type Mutations = {
  [MutationType.CreateTask](state: State, task: TaskItem): void;
  [MutationType.SetTasks](state: State, tasks: TaskItem[]): void;
  [MutationType.CompleteTask](
    state: State,
    task: Partial<TaskItem> & {
      id: number;
    }
  ): void;
  [MutationType.RemoveTask](
    state: State,
    task: Partial<TaskItem> & {
      id: number;
    }
  ): void;
  [MutationType.EditTask](
    state: State,
    task: Partial<TaskItem> & {
      id: number;
    }
  ): void;
  [MutationType.UpdateTask](
    state: State,
    task: Partial<TaskItem> & {
      id: number;
    }
  ): void;
  [MutationType.SetLoading](state: State, value: boolean): void;
  [MutationType.SetCreateModal](state: State, value: boolean): void;
  [MutationType.SetEditModal](
    state: State,
    value: {
      showModal: boolean;
      taskId: number | undefined;
    }
  ): void;
  [MutationType.SetTaskModal](
    state: State,
    value: {
      showModal: boolean;
      taskId: number | undefined;
    }
  ): void;
};

export const mutations: MutationTree<State> & Mutations = {
  [MutationType.CreateTask](state: State, task: TaskItem) {
    state.tasks.push(task);
  },
  [MutationType.SetTasks](state: State, tasks: TaskItem[]) {
    state.tasks = tasks;
  },
  [MutationType.CompleteTask](
    state: State,
    task: Partial<TaskItem> & {
      id: number;
    }
  ) {
    const taskIndex = state.tasks.findIndex((ele) => ele.id === task.id);
    if (taskIndex > -1) {
      state.tasks[taskIndex] = {
        ...state.tasks[taskIndex],
        ...task,
      };
    }
  },
  [MutationType.RemoveTask](
    state: State,
    task: Partial<TaskItem> & {
      id: number;
    }
  ) {
    const taskIndex = state.tasks.findIndex((ele) => ele.id === task.id);
    if (taskIndex > -1) {
      state.tasks.splice(taskIndex, 1);
    }
  },
  [MutationType.EditTask](
    state: State,
    task: Partial<TaskItem> & {
      id: number;
    }
  ) {
    const taskIndex = state.tasks.findIndex((ele) => ele.id === task.id);
    if (taskIndex > -1) {
      state.tasks[taskIndex] = {
        ...state.tasks[taskIndex],
        editing: !state.tasks[taskIndex].editing,
      };
      console.log('tasking editing', state.tasks[taskIndex]);
    }
  },
  [MutationType.UpdateTask](
    state: State,
    task: Partial<TaskItem> & {
      id: number;
    }
  ) {
    const taskIndex = state.tasks.findIndex((ele) => ele.id === task.id);
    if (taskIndex > -1) {
      state.tasks[taskIndex] = {
        ...state.tasks[taskIndex],
        ...task,
      };
    }
  },
  [MutationType.SetLoading](state: State, value: boolean) {
    state.loading = value;
  },
  [MutationType.SetCreateModal](state: State, value: boolean) {
    state.showCreateModal = value;
  },
  [MutationType.SetEditModal](
    state: State,
    value: {
      showModal: boolean;
      taskId: number | undefined;
    }
  ) {
    state.showEditModal = value.showModal;
    state.editModalTaskId = value.taskId;
  },
  [MutationType.SetTaskModal](
    state: State,
    value: {
      showModal: boolean;
      taskId: number | undefined;
    }
  ) {
    state.showTaskModal = value.showModal;
    state.showTaskId = value.taskId;
  },
};

再在 store/index.ts 倒入就好了

1
2
3
4
5
6
7
8
9
import { createStore } from 'vuex';
import { state } from './state';
import { mutations } from './mutations';
export default createStore({
  state,
  mutations,
  actions: {},
  modules: {},
});

Actions

我们接下来编写 Actions 相关的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import { ActionContext, ActionTree } from 'vuex';

import { Mutations, MutationType } from './mutations';
import { State } from './state';

export enum ActionTypes {
  GetTaskItems = 'GET_Task_ITEMS',
  SetCreateModal = 'SET_CREATE_MODAL',
  SetEditModal = 'SET_EDIT_MODAL',
}

type ActionArguments = Omit<ActionContext<State, State>, 'commit'> & {
  commit<K extends keyof Mutations>(
    key: K,
    payload: Parameters<Mutations[K]>[1]
  ): ReturnType<Mutations[K]>;
};

export type Actions = {
  [ActionTypes.GetTaskItems](context: ActionArguments): void;
  [ActionTypes.SetCreateModal](context: ActionArguments): void;
  [ActionTypes.SetEditModal](context: ActionArguments): void;
};

const sleep = (ms: number) =>
  new Promise((res) => setTimeout(() => res(true), ms));

export const actions: ActionTree<State, State> & Actions = {
  async [ActionTypes.GetTaskItems]({ commit }) {
    commit(MutationType.SetLoading, true);
    await sleep(1000);
    commit(MutationType.SetLoading, false);

    commit(MutationType.SetTasks, [
      {
        id: 1,
        title: 'learn Vue3 + TypeScript + Vuex4',
        description: 'good good study, day day up',
        createdBy: 'codytang',
        assignedTo: 'codytang',
        completed: false,
        editing: false,
      },
      {
        id: 2,
        title: 'miaomiaomiao',
        description: '喵咪~~~',
        createdBy: '🐱',
        assignedTo: '🐱',
        completed: false,
        editing: false,
      },
    ]);
  },
  [ActionTypes.SetCreateModal]({ commit }) {
    commit(MutationType.SetCreateModal, true);
  },
  [ActionTypes.SetEditModal]({ commit }) {
    commit(MutationType.SetEditModal, {
      showModal: true,
      taskId: 1,
    });
  },
};

再在 store/index.ts 倒入就好了

1
2
3
4
5
6
7
8
9
10
import { createStore } from 'vuex';
import { state } from './state';
import { mutations } from './mutations';
import { actions } from './actions';
export default createStore({
  state,
  mutations,
  actions,
  modules: {},
});

Getters

我们定义几个 Getters 辅助函数帮助我们快速获取一些值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { GetterTree } from 'vuex';
import { State, TaskItem } from './state';

export type Getters = {
  completedTaskCount(state: State): number;
  totalTaskCount(state: State): number;
  getTaskById(state: State): (id: number) => TaskItem | undefined;
};

export const getters: GetterTree<State, State> & Getters = {
  completedTaskCount(state: State) {
    return state.tasks.filter((item) => item.completed).length;
  },
  totalTaskCount(state: State) {
    return state.tasks.length;
  },
  getTaskById: (state: State) => (id: number) => {
    return state.tasks.find((task) => task.id === id);
  },
};

再在 store/index.ts 倒入就好了

1
2
3
4
5
6
7
8
9
10
11
12
import { createStore } from 'vuex';
import { state } from './state';
import { mutations } from './mutations';
import { actions } from './actions';
import { getters } from './getters';
export default createStore({
  state,
  mutations,
  actions,
  getters,
  modules: {},
});

基本的几个要素都齐全了,我们再来修改下我们的 Store

Store

调整成为下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import {
  createStore,
  Store as VuexStore,
  CommitOptions,
  DispatchOptions,
  createLogger,
} from 'vuex';
import { State, state } from './state';
import { Mutations, mutations } from './mutations';
import { Actions, actions } from './actions';
import { Getters, getters } from './getters';

export const store = createStore<State>({
  plugins: process.env.NODE_ENV === 'development' ? [createLogger()] : [],
  state,
  mutations,
  actions,
  getters,
  modules: {},
});

export function useStore() {
  return store as Store;
}

export type Store = Omit<
  VuexStore<State>,
  'getters' | 'commit' | 'dispatch'
> & {
  commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
    key: K,
    payload: P,
    options?: CommitOptions
  ): ReturnType<Mutations[K]>;
} & {
  dispatch<K extends keyof Actions>(
    key: K,
    payload?: Parameters<Actions[K]>[1],
    options?: DispatchOptions
  ): ReturnType<Actions[K]>;
} & {
  getters: {
    [K in keyof Getters]: ReturnType<Getters[K]>;
  };
};

另外因为修改了导出的行为, main.ts 也需要同步修改

1
2
3
4
5
import { createApp } from 'vue';
import App from './App.vue';
import { store } from './store';

createApp(App).use(store).mount('#app');

状态管理部分算是完成了,我们开始编写我们的 App 去使用它

App

接下来简单改写了一下 App.vue 文件。首先看模板,其实就是 loading 的时候展示 loading...提示,loading 结束之后展示一些任务相关的信息

另外我们看到脚本部分,Vue3Vue2 区别还是很大的,这些是 Vue3 里面新增的 Composition Api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template>
  <div class="container mx-auto mt-4">
    <h1 class="is-size-3 has-text-centered p-2 has-text-weight-bold">
      任务管理应用
    </h1>
    <div v-if="loading">
      <h3 class="has-text-centered mt-4">Loading...</h3>
    </div>
    <div v-else>
      <p class="has-text-centered mt-2">
         of  completed.
      </p>
      <!-- <TaskList /> -->
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, computed, onMounted } from 'vue';
import { useStore } from './store';
import { ActionTypes } from './store/actions';

export default defineComponent({
  name: 'App',
  setup() {
    const store = useStore();
    const loading = computed(() => store.state.loading);
    const completedCount = computed(() => store.getters.completedTaskCount);
    const totalTaskCount = computed(() => store.getters.totalTaskCount);

    onMounted(() => store.dispatch(ActionTypes.GetTaskItems));

    return {
      loading,
      completedCount,
      totalTaskCount,
    };
  },
});
</script>

<style>
@import '~bulma/css/bulma.css';
</style>

接下来编写 TaskList 组件

TaskList

先把大概的结构写出来,再慢慢补充细节上去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<template>
  <table class="table">
    <thead>
      <tr>
        <th><abbr title="Position">Task Id</abbr></th>
        <th>Completed</th>
        <th>Task</th>
        <th><abbr title="Won">Created By</abbr></th>
        <th><abbr title="Drawn">Assigned To</abbr></th>
        <th><abbr title="Lost">Actions</abbr></th>
      </tr>
    </thead>
    <tbody v-if="tasks">
      <template v-for="task in tasks" :key="task.id">
        <!-- <TaskListItem v-bind="task" /> -->
      </template>
    </tbody>
    <tfoot>
      <!-- <CreateModal v-show="showCreateModal"></CreateModal> -->
      <button class="button is-link" @click="setModal">Create Task</button>
    </tfoot>
  </table>
  <!-- <EditModal v-if="showEditModal" :id="editTaskId"></EditModal> -->
  <!-- <TaskItem v-if="showTaskModal" :id="showTaskId"></TaskItem> -->
</template>

<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useStore } from '../store';
import { MutationType } from '@/store/mutations';
export default defineComponent({
  setup() {
    const store = useStore();
    const setModal = () => store.commit(MutationType.SetCreateModal, true);
    const tasks = computed(() => store.state.tasks);
    const showCreateModal = computed(() => store.state.showCreateModal);

    const showEditModal = computed(() => store.state.showEditModal);
    const editTaskId = computed(() => store.state.editModalTaskId);

    const showTaskModal = computed(() => store.state.showTaskModal);
    const showTaskId = computed(() => store.state.showTaskId);

    return {
      showCreateModal,
      setModal,
      tasks,
      showEditModal,
      showTaskModal,
      editTaskId,
      showTaskId,
    };
  },
});
</script>

<style>
table {
  width: 100%;
}
.fa {
  font-size: 1.2rem;
  margin-left: 15px;
}
.fa:hover {
  font-size: 1.4rem;
}
</style>

TaskListItem

编写任务列表中每一项的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<template>
  <tr>
    <th></th>
    <td>
      <input
        type="checkbox"
        :checked="completed"
        @change="toggleCompletion()"
      />
    </td>
    <td> <strong>(C)</strong></td>
    <td></td>
    <td></td>
    <td>
      <span @click="viewTask()"> View </span>
      <span @click="editTask()"> Edit </span>
      <span @click="removeTask()"> Remove </span>
    </td>
  </tr>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { useStore } from '../store';
import { MutationType } from '../store/mutations';

export default defineComponent({
  props: {
    id: { type: Number, required: true },
    title: { type: String, required: true },
    createdBy: { type: String, required: true },
    assignedTo: { type: String, required: true },
    completed: { type: Boolean, required: true },
  },
  setup(props) {
    const store = useStore();

    const toggleCompletion = () => {
      store.commit(MutationType.CompleteTask, {
        id: props.id,
        completed: !props.completed,
      });
    };

    const viewTask = () => {
      store.commit(MutationType.SetTaskModal, {
        taskId: props.id,
        showModal: true,
      });
    };

    const removeTask = () => {
      store.commit(MutationType.RemoveTask, {
        id: props.id,
      });
    };

    const editTask = () => {
      store.commit(MutationType.SetEditModal, {
        taskId: props.id,
        showModal: true,
      });
    };

    return {
      toggleCompletion,
      viewTask,
      removeTask,
      editTask,
    };
  },
});
</script>

TaskItem

接下来写下点击查看任务时候的蒙层组件 TaskItem

这个组件主要处理信息的展示,以及关掉弹窗的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<template>
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-content">
      <div class="card">
        <div class="card-content">
          <div class="media">
            <div class="media-content">
              <p class="title is-4">标题: </p>
            </div>
          </div>
          <div class="content">
            <p class="subtitle is-6"><b>分配给:</b> </p>
            <p class="subtitle is-6"><b>创建人:</b> </p>
            <p class="subtitle is-6">描述: </p>
          </div>
        </div>
      </div>
    </div>
    <button
      class="modal-close is-large"
      @click="closeModal"
      aria-label="close"
    ></button>
  </div>
</template>
<script lang="ts">
import { MutationType } from '@/store/mutations';
import { computed, defineComponent } from 'vue';
import { useStore } from 'vuex';

export default defineComponent({
  props: {
    id: {
      type: Number,
      required: true,
    },
  },
  setup(props) {
    console.log(1);
    const store = useStore();
    const task = computed(() => store.getters.getTaskById(Number(props.id)));

    const closeModal = () => {
      store.commit(MutationType.SetTaskModal, {
        showModal: false,
        taskId: undefined,
      });
    };

    return {
      task,
      closeModal,
    };
  },
});
</script>

CreateModal

接下来我们处理下创建任务的弹窗

...toRefs(state) 可以把响应式对象的属性,平铺出来,继续保持响应式,直接在模板中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<template>
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-content">
      <div class="card">
        <div class="card-content">
          <form @submit.prevent="createTask">
            <div class="field">
              <label class="label">Task Title</label>
              <div class="control">
                <input
                  v-model="title"
                  class="input"
                  type="text"
                  placeholder="Enter task"
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Description</label>
              <div class="control">
                <textarea
                  v-model="description"
                  class="textarea"
                  placeholder="Textarea"
                ></textarea>
              </div>
            </div>
            <div class="field">
              <label class="label">Assigned By</label>
              <div class="control">
                <input
                  v-model="createdBy"
                  class="input"
                  type="text"
                  placeholder="Enter Assigner's name"
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Assigned To</label>
              <div class="control">
                <input
                  v-model="assignedTo"
                  class="input"
                  type="text"
                  placeholder="Enter task creator's name"
                />
              </div>
            </div>
            <div class="field is-grouped">
              <div class="control">
                <button type="submit" class="button is-link">Submit</button>
              </div>
              <div class="control" @click="closeModal">
                <button class="button is-link is-light">Cancel</button>
              </div>
            </div>
          </form>
        </div>
      </div>
    </div>
    <button
      class="modal-close is-large"
      @click="closeModal"
      aria-label="close"
    ></button>
  </div>
</template>
<script lang="ts">
import { MutationType } from '@/store/mutations';
import { defineComponent, reactive, toRefs } from 'vue';
import { useStore } from 'vuex';

export default defineComponent({
  setup() {
    const store = useStore();
    const state = reactive({
      title: '',
      description: '',
      createdBy: '',
      assignedTo: '',
    });

    const closeModal = () => {
      store.commit(MutationType.SetCreateModal, false);
    };

    const createTask = () => {
      if (
        !state.title ||
        !state.description ||
        !state.createdBy ||
        !state.assignedTo
      )
        return;

      const task = {
        id: Date.now(),
        title: state.title,
        description: state.description,
        createdBy: state.createdBy,
        assignedTo: state.assignedTo,
        completed: false,
        editing: false,
      };

      store.commit(MutationType.CreateTask, task);
      state.title = '';
      state.createdBy = '';
      state.assignedTo = '';
      state.description = '';
    };

    return {
      state,
      closeModal,
      createTask,
      ...toRefs(state),
    };
  },
});
</script>

EditModal

再来看看编辑的弹窗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<template>
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-content">
      <div class="card">
        <div class="card-content">
          <form @submit.prevent="updateTask">
            <h1>Edit Modal</h1>
            <div class="field">
              <label class="label">Task Title</label>
              <div class="control">
                <input
                  v-model="title"
                  class="input"
                  type="text"
                  placeholder="Enter task"
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Description</label>
              <div class="control">
                <textarea
                  v-model="description"
                  class="textarea"
                  placeholder="Textarea"
                ></textarea>
              </div>
            </div>
            <div class="field">
              <label class="label">Assigned By</label>
              <div class="control">
                <input
                  v-model="createdBy"
                  class="input"
                  type="text"
                  placeholder="Enter Assigner's name"
                />
              </div>
            </div>
            <div class="field">
              <label class="label">Assigned To</label>
              <div class="control">
                <input
                  v-model="assignedTo"
                  class="input"
                  type="text"
                  placeholder="Enter task creator's name"
                />
              </div>
            </div>
            <div class="field is-grouped">
              <div class="control">
                <button type="submit" class="button is-link">Submit</button>
              </div>
              <div class="control" @click="closeModal">
                <button class="button is-link is-light">Cancel</button>
              </div>
            </div>
          </form>
        </div>
        <button
          class="modal-close is-large"
          @click="closeModal"
          aria-label="close"
        ></button>
      </div>
    </div>
  </div>
</template>
<script lang="ts">
import { useStore } from '@/store';
import { MutationType } from '@/store/mutations';
import { defineComponent, onMounted, reactive, toRefs } from 'vue';

export default defineComponent({
  props: {
    id: {
      type: Number,
      required: true,
    },
  },
  setup(props) {
    const store = useStore();
    const state = reactive({
      title: '',
      description: '',
      createdBy: '',
      assignedTo: '',
    });

    const setFields = () => {
      const task = store.getters.getTaskById(Number(props.id));
      if (task) {
        const { title, description, createdBy, assignedTo } = task;
        state.title = title;
        state.description = description;
        state.createdBy = createdBy;
        state.assignedTo = assignedTo;
      }
    };

    const closeModal = () => {
      store.commit(MutationType.SetEditModal, {
        showModal: false,
        taskId: undefined,
      });
    };

    onMounted(() => setFields());

    const updateTask = () => {
      if (
        !state.title ||
        !state.description ||
        !state.createdBy ||
        !state.assignedTo
      )
        return;

      const task = {
        id: props.id,
        title: state.title,
        description: state.description,
        createdBy: state.createdBy,
        assignedTo: state.assignedTo,
        completed: false,
        editing: false,
      };

      store.commit(MutationType.UpdateTask, task);

      state.title = '';
      state.createdBy = '';
      state.assignedTo = '';
      state.description = '';

      closeModal();
    };

    return {
      ...toRefs(state),
      closeModal,
      updateTask,
    };
  },
});
</script>

再续 TaskList

前面注释的组件现在都补上了,所以最终组件展示成下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<template>
  <table class="table">
    <thead>
      <tr>
        <th><abbr title="Position">Task Id</abbr></th>
        <th>Completed</th>
        <th>Task</th>
        <th><abbr title="Won">Created By</abbr></th>
        <th><abbr title="Drawn">Assigned To</abbr></th>
        <th><abbr title="Lost">Actions</abbr></th>
      </tr>
    </thead>
    <tbody v-if="tasks">
      <template v-for="task in tasks" :key="task.id">
        <TaskListItem v-bind="task" />
      </template>
    </tbody>
    <tfoot>
      <CreateModal v-show="showCreateModal"></CreateModal>
      <button class="button is-link" @click="setModal">Create Task</button>
    </tfoot>
  </table>
  <EditModal v-if="showEditModal" :id="editTaskId"></EditModal>
  <TaskItem v-if="showTaskModal" :id="showTaskId"></TaskItem>
</template>

<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useStore } from '../store';
import { MutationType } from '@/store/mutations';
import TaskListItem from './TaskListItem.vue';
import TaskItem from './TaskItem.vue';
import CreateModal from './CreateModal.vue';
import EditModal from './EditModal.vue';
export default defineComponent({
  components: {
    TaskListItem,
    TaskItem,
    CreateModal,
    EditModal,
  },
  setup() {
    const store = useStore();
    const setModal = () => store.commit(MutationType.SetCreateModal, true);
    const tasks = computed(() => store.state.tasks);
    const showCreateModal = computed(() => store.state.showCreateModal);

    const showEditModal = computed(() => store.state.showEditModal);
    const editTaskId = computed(() => store.state.editModalTaskId);

    const showTaskModal = computed(() => store.state.showTaskModal);
    const showTaskId = computed(() => store.state.showTaskId);

    return {
      showCreateModal,
      setModal,
      tasks,
      showEditModal,
      showTaskModal,
      editTaskId,
      showTaskId,
    };
  },
});
</script>

<style>
table {
  width: 100%;
}
</style>

结语

这篇文章整理自 Building Web Apps with Vue 3 composition API + Typescript + Vuex(4.0)

过程也发现了原来的文章展示出来的代码有一些问题,在学习的过程中进行了修改,保证案例可正常运行~

over ~