{"id":197,"date":"2025-05-03T20:06:53","date_gmt":"2025-05-03T11:06:53","guid":{"rendered":"https:\/\/suyasuyahirohiro.com\/?p=197"},"modified":"2025-05-03T20:06:53","modified_gmt":"2025-05-03T11:06:53","slug":"%e3%83%9a%e3%83%bc%e3%82%b8%e3%82%92%e7%a7%bb%e5%8b%95%e3%81%99%e3%82%8b%e3%81%a8%e3%82%bf%e3%82%a4%e3%83%9e%e3%83%bc%e3%81%8c%e3%83%aa%e3%82%bb%e3%83%83%e3%83%88%e3%81%95%e3%82%8c%e3%81%a6%e3%81%97","status":"publish","type":"post","link":"https:\/\/suyasuyahirohiro.com\/?p=197","title":{"rendered":"\u30da\u30fc\u30b8\u3092\u79fb\u52d5\u3059\u308b\u3068\u30bf\u30a4\u30de\u30fc\u304c\u30ea\u30bb\u30c3\u30c8\u3055\u308c\u3066\u3057\u307e\u3046"},"content":{"rendered":"\n<p>\u30dd\u30e2\u30c9\u30fc\u30ed\u30bf\u30a4\u30de\u30fc\u304c\u30da\u30fc\u30b8\u9077\u79fb\u3084\u30ea\u30ed\u30fc\u30c9\u5f8c\u306b\u521d\u671f\u5316\u3055\u308c\u3066\u3057\u307e\u3046\u539f\u56e0\u306f\u3001\u72b6\u614b\uff08\u6b8b\u308a\u6642\u9593\u3084\u30bf\u30a4\u30de\u30fc\u306e\u9032\u884c\u72b6\u6cc1\u306a\u3069\uff09\u3092<strong>\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u306e\u30ed\u30fc\u30ab\u30eb\u72b6\u614b\u306b\u306e\u307f\u4fdd\u6301\u3057\u3066\u3044\u308b\u305f\u3081<\/strong>\u3067\u3059\u3002\u3053\u308c\u3092\u9632\u3050\u305f\u3081\u306b\u306f\u3001\u72b6\u614b\u3092<strong>\u5916\u90e8\u30b9\u30c8\u30a2\uff08\u4f8b\uff1aPinia\uff09\u306b\u4fdd\u6301<\/strong>\u3057\u3001<strong>\u5fc5\u8981\u306b\u5fdc\u3058\u3066\u30ed\u30fc\u30ab\u30eb\u30b9\u30c8\u30ec\u30fc\u30b8\u3068\u540c\u671f<\/strong>\u3059\u308b\u3053\u3068\u3067\u30ea\u30ed\u30fc\u30c9\u8010\u6027\u3092\u6301\u305f\u305b\u3089\u308c\u307e\u3059\u3002<\/p>\n\n\n\n<p>\u4ee5\u4e0b\u306b\u3001<strong>Pinia\u3092\u4f7f\u3063\u3066\u72b6\u614b\u7ba1\u7406\u3092\u5c0e\u5165\u3057\u3001\u30ea\u30ed\u30fc\u30c9\u5f8c\u306b\u3082\u30bf\u30a4\u30de\u30fc\u304c\u7d99\u7d9a\u3059\u308b\u3088\u3046\u306b\u4fee\u6b63\u3057\u305f\u30b3\u30fc\u30c9\u3068\u89e3\u8aac<\/strong>\u3092\u8a18\u8f09\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 \u4fee\u6b63\u624b\u9806\u307e\u3068\u3081<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Pinia\u306e\u30b9\u30c8\u30a2\u3092\u4f5c\u6210<\/strong>\u3057\u3066\u3001\u30bf\u30a4\u30de\u30fc\u72b6\u614b\u3092\u4e00\u5143\u7ba1\u7406\u3002<\/li>\n\n\n\n<li><strong><code>PomodoroTimer.vue<\/code> \u304b\u3089\u30b9\u30c8\u30a2\u3092\u5229\u7528<\/strong>\u3057\u3066\u3001\u72b6\u614b\u3092\u30ed\u30fc\u30ab\u30eb\u3067\u306f\u306a\u304f\u30b9\u30c8\u30a2\u306b\u4fdd\u5b58\u3002<\/li>\n\n\n\n<li><strong>\u72b6\u614b\u3092 localStorage \u306b\u4fdd\u5b58\u30fb\u5fa9\u5143<\/strong>\u3057\u3066\u3001\u30ea\u30ed\u30fc\u30c9\u5bfe\u7b56\u3002<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udce6 1. <code>stores\/usePomodoroStore.js<\/code> \u3092\u4f5c\u6210<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">js\u30b3\u30d4\u30fc\u3059\u308b\u7de8\u96c6\u3059\u308b<code>\/\/ stores\/usePomodoroStore.js\nimport { defineStore } from \"pinia\";\n\nexport const usePomodoroStore = defineStore(\"pomodoro\", {\n  state: () =&gt; ({\n    isRunning: false,\n    isBreakTime: false,\n    remainingSeconds: 1500, \/\/ \u521d\u671f25\u5206\n    workDuration: 25,\n    breakDuration: 5,\n    lastTimestamp: null,\n  }),\n  actions: {\n    start(duration) {\n      this.isRunning = true;\n      this.remainingSeconds = duration;\n      this.lastTimestamp = Date.now();\n      this.saveToStorage();\n    },\n    tick() {\n      const now = Date.now();\n      const elapsed = Math.floor((now - this.lastTimestamp) \/ 1000);\n      this.remainingSeconds -= elapsed;\n      this.lastTimestamp = now;\n      this.saveToStorage();\n    },\n    reset() {\n      this.isRunning = false;\n      this.isBreakTime = false;\n      this.remainingSeconds = this.workDuration * 60;\n      this.lastTimestamp = null;\n      this.saveToStorage();\n    },\n    saveToStorage() {\n      localStorage.setItem(\"pomodoroState\", JSON.stringify(this.$state));\n    },\n    loadFromStorage() {\n      const saved = localStorage.getItem(\"pomodoroState\");\n      if (saved) {\n        Object.assign(this, JSON.parse(saved));\n      }\n    },\n  },\n});\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83e\udded 2. <code>PomodoroTimer.vue<\/code> \u306e\u4fee\u6b63<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">vue\u30b3\u30d4\u30fc\u3059\u308b\u7de8\u96c6\u3059\u308b<code>&lt;script setup&gt;\nimport { computed, onMounted, onUnmounted } from \"vue\";\nimport { usePomodoroStore } from \"..\/stores\/usePomodoroStore\";\n\nconst store = usePomodoroStore();\n\nonMounted(() =&gt; {\n  store.loadFromStorage();\n  if (store.isRunning) {\n    timerInterval = setInterval(() =&gt; {\n      store.tick();\n    }, 1000);\n  }\n});\n\nonUnmounted(() =&gt; {\n  clearInterval(timerInterval);\n  store.saveToStorage();\n});\n\nlet timerInterval = null;\n\nconst formattedTime = computed(() =&gt; {\n  const minutes = String(Math.floor(store.remainingSeconds \/ 60)).padStart(2, \"0\");\n  const seconds = String(store.remainingSeconds % 60).padStart(2, \"0\");\n  return `${minutes}:${seconds}`;\n});\n\nfunction startTimer() {\n  if (store.isRunning) return;\n  const duration = (store.isBreakTime ? store.breakDuration : store.workDuration) * 60;\n  store.start(duration);\n  timerInterval = setInterval(() =&gt; {\n    store.tick();\n  }, 1000);\n}\n\nfunction resetTimer() {\n  clearInterval(timerInterval);\n  store.reset();\n}\n&lt;\/script&gt;\n\n&lt;template&gt;\n  &lt;div class=\"pomodoro-timer\"&gt;\n    &lt;h2&gt;{{ store.isBreakTime ? \"\u4f11\u61a9\u30bf\u30a4\u30de\u30fc\" : \"\u30dd\u30e2\u30c9\u30fc\u30ed\u4f5c\u696d\u30bf\u30a4\u30de\u30fc\" }}&lt;\/h2&gt;\n    &lt;p&gt;{{ formattedTime }}&lt;\/p&gt;\n    &lt;button @click=\"startTimer\" :disabled=\"store.isRunning\"&gt;\u958b\u59cb&lt;\/button&gt;\n    &lt;button @click=\"resetTimer\"&gt;\u30ea\u30bb\u30c3\u30c8&lt;\/button&gt;\n    &lt;div&gt;\n      &lt;label&gt;\u4f5c\u696d\u6642\u9593\uff08\u5206\uff09:&lt;\/label&gt;\n      &lt;input type=\"number\" v-model=\"store.workDuration\" :disabled=\"store.isRunning\" \/&gt;\n    &lt;\/div&gt;\n    &lt;div&gt;\n      &lt;label&gt;\u4f11\u61a9\u6642\u9593\uff08\u5206\uff09:&lt;\/label&gt;\n      &lt;input type=\"number\" v-model=\"store.breakDuration\" :disabled=\"store.isRunning\" \/&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n&lt;\/template&gt;\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udee0 \u4fee\u6b63\u70b9\u306e\u89e3\u8aac<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\u4fee\u6b63\u5185\u5bb9<\/th><th>\u89e3\u8aac<\/th><\/tr><\/thead><tbody><tr><td><code>Pinia<\/code> \u30b9\u30c8\u30a2\u3067\u72b6\u614b\u3092\u7ba1\u7406<\/td><td><code>isRunning<\/code> \u3084 <code>remainingSeconds<\/code> \u3092\u5168\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u3067\u5171\u6709\u53ef\u80fd\u306b<\/td><\/tr><tr><td><code>localStorage<\/code> \u3068\u540c\u671f<\/td><td>\u30ea\u30ed\u30fc\u30c9\u5f8c\u3082\u30bf\u30a4\u30de\u30fc\u304c\u7d99\u7d9a\u3055\u308c\u308b\u3088\u3046\u306b\u5fa9\u5143<\/td><\/tr><tr><td>\u30bf\u30a4\u30de\u30fc\u66f4\u65b0 (<code>tick<\/code>) \u306e\u5236\u5fa1<\/td><td><code>setInterval<\/code> \u3067\u7d4c\u904e\u6642\u9593\u3092\u53cd\u6620\u3057\u3001\u6b8b\u308a\u6642\u9593\u3092\u66f4\u65b0<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 \u6700\u5f8c\u306b<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>main.js<\/code> \u307e\u305f\u306f <code>main.ts<\/code> \u306b\u3066 Pinia \u3092\u767b\u9332\u3057\u3066\u304f\u3060\u3055\u3044\uff1a<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-preformatted\">js\u30b3\u30d4\u30fc\u3059\u308b\u7de8\u96c6\u3059\u308b<code>import { createApp } from \"vue\";\nimport { createPinia } from \"pinia\";\nimport App from \".\/App.vue\";\n\nconst app = createApp(App);\napp.use(createPinia());\napp.mount(\"#app\");\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\u3053\u306e\u5b9f\u88c5\u3067\u300c\u30ab\u30ec\u30f3\u30c0\u30fc\u304b\u3089\u623b\u3063\u305f\u3089\u521d\u671f\u5316\u3055\u308c\u308b\u300d\u554f\u984c\u306f\u89e3\u6d88\u3055\u308c\u3001\u30ea\u30ed\u30fc\u30c9\u5f8c\u306b\u3082\u30bf\u30a4\u30de\u30fc\u304c\u7d99\u7d9a\u3055\u308c\u308b\u3088\u3046\u306b\u306a\u308a\u307e\u3059\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u30dd\u30e2\u30c9\u30fc\u30ed\u30bf\u30a4\u30de\u30fc\u304c\u30da\u30fc\u30b8\u9077\u79fb\u3084\u30ea\u30ed\u30fc\u30c9\u5f8c\u306b\u521d\u671f\u5316\u3055\u308c\u3066\u3057\u307e\u3046\u539f\u56e0\u306f\u3001\u72b6\u614b\uff08\u6b8b\u308a\u6642\u9593\u3084\u30bf\u30a4\u30de\u30fc\u306e\u9032\u884c\u72b6\u6cc1\u306a\u3069\uff09\u3092\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u306e\u30ed\u30fc\u30ab\u30eb\u72b6\u614b\u306b\u306e\u307f\u4fdd\u6301\u3057\u3066\u3044\u308b\u305f\u3081\u3067\u3059\u3002\u3053\u308c\u3092\u9632\u3050\u305f\u3081\u306b\u306f\u3001\u72b6\u614b\u3092\u5916\u90e8\u30b9 &#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-197","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/suyasuyahirohiro.com\/index.php?rest_route=\/wp\/v2\/posts\/197","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/suyasuyahirohiro.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/suyasuyahirohiro.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/suyasuyahirohiro.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/suyasuyahirohiro.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=197"}],"version-history":[{"count":1,"href":"https:\/\/suyasuyahirohiro.com\/index.php?rest_route=\/wp\/v2\/posts\/197\/revisions"}],"predecessor-version":[{"id":198,"href":"https:\/\/suyasuyahirohiro.com\/index.php?rest_route=\/wp\/v2\/posts\/197\/revisions\/198"}],"wp:attachment":[{"href":"https:\/\/suyasuyahirohiro.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=197"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/suyasuyahirohiro.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=197"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/suyasuyahirohiro.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=197"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}