-
Notifications
You must be signed in to change notification settings - Fork 1
composition-api を用いて todo-app を作ってみる #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
dbea36a
0e1e9a6
119182a
082b283
22da6c0
1e31f21
41d5162
675b103
0124007
35820fa
7ef7476
0c3076f
8ff35c2
528ebc2
e395b8f
94f0bd1
f818b8b
a1c6afa
cd30489
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,11 @@ | ||
<template> | ||
<div id="nav"> | ||
<router-link to="/">Home</router-link> | | ||
<router-link to="/about">About</router-link> | ||
</div> | ||
<router-view /> | ||
</template> | ||
|
||
<style> | ||
html, | ||
body, | ||
#app { | ||
font-family: Avenir, Helvetica, Arial, sans-serif; | ||
-webkit-font-smoothing: antialiased; | ||
-moz-osx-font-smoothing: grayscale; | ||
text-align: center; | ||
color: #2c3e50; | ||
} | ||
|
||
#nav { | ||
padding: 30px; | ||
} | ||
|
||
#nav a { | ||
font-weight: bold; | ||
color: #2c3e50; | ||
} | ||
|
||
#nav a.router-link-exact-active { | ||
color: #42b983; | ||
height: 100%; | ||
} | ||
</style> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<template> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
:class="$props.class" | ||
:viewBox="`0 0 ${width} ${height}`" | ||
> | ||
<path fill="currentColor" :d="svgPath" /> | ||
</svg> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent, computed, PropType } from "vue"; | ||
import { | ||
findIconDefinition, | ||
IconName | ||
} from "@fortawesome/fontawesome-svg-core"; | ||
|
||
export default defineComponent({ | ||
name: "FontAwesomeIcon", | ||
|
||
props: { | ||
icon: { | ||
type: String as PropType<IconName>, | ||
required: true | ||
}, | ||
type: { | ||
type: String as PropType<"fas" | "fal" | "far">, | ||
default: "fas" | ||
}, | ||
class: String | ||
}, | ||
|
||
setup(props) { | ||
const definition = computed(() => | ||
findIconDefinition({ | ||
prefix: props.type, | ||
iconName: props.icon | ||
}) | ||
); | ||
|
||
const width = computed(() => definition.value.icon[0]); | ||
const height = computed(() => definition.value.icon[1]); | ||
const svgPath = computed(() => definition.value.icon[4]); | ||
|
||
return { width, height, svgPath }; | ||
} | ||
}); | ||
</script> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<template> | ||
<form @submit.prevent="handleSubmitNewTodo"> | ||
<input | ||
type="text" | ||
placeholder="Add new todo" | ||
required | ||
class="form-control px-5 py-4" | ||
v-model.trim="state.title" | ||
/> | ||
</form> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent, PropType, reactive, watch } from "vue"; | ||
|
||
export type HandleSubmitNewTodo = () => void; | ||
|
||
type State = { | ||
title: string; | ||
}; | ||
|
||
export default defineComponent({ | ||
props: { | ||
newTodoTitle: { | ||
type: String as PropType<string>, | ||
required: true | ||
}, | ||
handleSubmitNewTodo: { | ||
type: Function as PropType<HandleSubmitNewTodo>, | ||
required: true | ||
} | ||
}, | ||
|
||
emits: ["update:newTodoTitle"], | ||
|
||
setup(props, context) { | ||
const state = reactive<State>({ title: props.newTodoTitle }); | ||
watch( | ||
() => props.newTodoTitle, | ||
newValue => { | ||
state.title = newValue; | ||
} | ||
); | ||
watch( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. あんまりwatch多用するとコード追いにくくなるので、普通にeventで処理できるところではイベント使う派です。(ここでは@input使う) |
||
() => state.title, | ||
newValue => { | ||
context.emit("update:newTodoTitle", newValue); | ||
} | ||
); | ||
return { state }; | ||
} | ||
}); | ||
</script> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<template> | ||
<form @submit.prevent="handleSubmitEditedTodoAndReset(todoBeingEdited)"> | ||
<input | ||
ref="input" | ||
type="text" | ||
class="form-control px-5 py-4" | ||
v-model.trim="todoBeingEdited.title" | ||
@blur="handleCancelEdit" | ||
required | ||
/> | ||
</form> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent, PropType, Ref, ref, onMounted } from "vue"; | ||
import type Todo from "@/models/todo"; | ||
|
||
export type HandleCancelEdit = () => void; | ||
export type HandleSubmitEditedTodoAndReset = (editedTodo: Todo) => void; | ||
|
||
export default defineComponent({ | ||
props: { | ||
todo: { | ||
type: Object as PropType<Todo>, | ||
required: true, | ||
}, | ||
handleSubmitEditedTodoAndReset: { | ||
type: Function as PropType<HandleSubmitEditedTodoAndReset>, | ||
required: true, | ||
}, | ||
handleCancelEdit: { | ||
type: Function as PropType<HandleCancelEdit>, | ||
required: true, | ||
}, | ||
}, | ||
|
||
setup(props) { | ||
const todoBeingEdited: Todo = { ...props.todo }; | ||
r-toki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const input: Ref<HTMLInputElement | null> = ref(null); | ||
onMounted(() => { | ||
input.value?.focus(); | ||
r-toki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
return { todoBeingEdited, input }; | ||
}, | ||
}); | ||
</script> |
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,67 @@ | ||||||
<template> | ||||||
<li class="list-group-item d-flex justify-content-between"> | ||||||
<div> | ||||||
<input | ||||||
type="checkbox" | ||||||
class="mr-3" | ||||||
required | ||||||
:checked="todo.completed" | ||||||
@change="handleToggleCompleted(todo)" | ||||||
/> | ||||||
<span>{{ todo.title }}</span> | ||||||
</div> | ||||||
<div class="my-n2"> | ||||||
<button class="btn px-1 mr-1" @click="handleClickEdit(todo)"> | ||||||
<fa icon="edit" class="edit-icon"></fa> | ||||||
</button> | ||||||
<button class="btn px-1" @click="handleClickRemove(todo)"> | ||||||
<fa icon="trash" class="trash-icon"></fa> | ||||||
</button> | ||||||
</div> | ||||||
</li> | ||||||
</template> | ||||||
|
||||||
<script lang="ts"> | ||||||
import { defineComponent, PropType } from "vue"; | ||||||
import { FontAwesomeIcon } from "@/plugins/font-awesome"; | ||||||
import Todo from "@/models/todo"; | ||||||
|
||||||
export type HandleToggleCompleted = (todo: Todo) => void; | ||||||
export type HandleClickEdit = (todo: Todo) => void; | ||||||
export type HandleClickRemove = (todo: Todo) => void; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 好み? 自分ならHandleClickEditとHandleClickRemoveの引数はidだけにするかも。 |
||||||
|
||||||
export default defineComponent({ | ||||||
components: { fa: FontAwesomeIcon }, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 好み 自分なら↓みたいに書いて、コンポーネントと通常のhtmlタグを視覚的にも区別しやすくします。
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. kebabケースで書く場合は、ハイフン入りの名前が推奨と公式ガイドにあったはずです。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 修正しました。 |
||||||
|
||||||
props: { | ||||||
todo: { | ||||||
type: Object as PropType<Todo>, | ||||||
required: true | ||||||
}, | ||||||
handleToggleCompleted: { | ||||||
type: Function as PropType<HandleToggleCompleted>, | ||||||
required: true | ||||||
}, | ||||||
handleClickEdit: { | ||||||
type: Function as PropType<HandleClickEdit>, | ||||||
required: true | ||||||
}, | ||||||
handleClickRemove: { | ||||||
type: Function as PropType<HandleClickRemove>, | ||||||
required: true | ||||||
} | ||||||
} | ||||||
}); | ||||||
</script> | ||||||
|
||||||
<style scoped> | ||||||
.edit-icon { | ||||||
width: 15px; | ||||||
height: 15px; | ||||||
} | ||||||
|
||||||
.trash-icon { | ||||||
width: 15px; | ||||||
height: 15px; | ||||||
} | ||||||
</style> |
Uh oh!
There was an error while loading. Please reload this page.