Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Full-Stack Development ด้วย Vue, Vuex, และ Rails: แนวทาง Scalable

การ Build Full-stack Applications ด้วย Scalability ในใจ intimidating (ทำให้เฉยเมย/กลัว) โดยเฉพาะ Build ด้วย Latest Version ของ Vue และ Vuex ที่มี Full TypeScript Support บทความนี้จะสอนทุกอย่างที่ Readers ต้องรู้เกี่ยวกับ Building Scalable Full-stack Applications จาก State Management ด้วย Vuex 4.0 จัดการ API Requests และ Database Interactions โดยสำรวจ CRUD Application ที่ Manage Prescription ของ Treatments ให้ Unhealthy Livestock Backend สร้างด้วย Rails Expose Basic CRUD APIs สำหรับ Integration โดย Frontend

Most Companies Adopted API-based Development Architecture เนื่องจากให้ Frontend Team Flexibility เลือก Frontend Tool ที่ Suitable แม้ไม่ใช่ Best Option แต่มัน Reliable Option และหลาย Companies Adopted Development Architecture นี้เพราะเพิ่ม Flexibility ให้ Team

Prerequisites

ก่อนเริ่ม ต้องมีสิ่งต่อไปนี้เพื่อ Keep Up กับ Tutorial:

  • Rails V6.x
  • Node.js V10.x
  • Prior Working Knowledge of Ruby on Rails
  • Prior Working Knowledge of TypeScript
  • Prior Working Knowledge of Vue.js

What we will build

เชี่ยวชาญ Full-Stack Development ด้วย Vue, Vuex, และ Rails: แนวทาง Scalable

ใน Tutorial นี้ เราจะ Build Full-stack CRUD Application Manage Prescription of Treatments สำหรับ Unhealthy Livestock Users สามารถ Create, Update, และ Delete Prescriptions สำหรับ Livestock CRUD APIs สร้างด้วย Rails Expose CRUD APIs สำหรับ Integration โดย Frontend สร้างด้วย Vue 3 แต่ละ Stack รันบน Different Ports เป็น Independent Applications

Basic Architecture of the Full-stack App

Full-stack Application ของเราประกอบด้วย Client และ Server Applications รัน Independently แต่ละ Component ของ Client Application Interact กับ CRUD APIs ที่ Server Issue ผ่าน Vuex สำหรับ Proper Management ของ Application State Backend Application Store Prescription Data ทั้งหมดใน Sqlite 3 Database ขณะ Expose CRUD APIs ให้ Frontend Application

Backend Service Setup

ใน Folder ที่ชอบ สร้าง Rails App ด้วย Command:

rails new vet_clinic_api --api 

Flag --api บอก Rails สร้าง Project as an API ทำให้ Remove Frontend Dependencies (View Files)

Database Configuration

เราจะใช้ Sqlite 3 Default Database สำหรับ Rails Applications

rails g scaffold prescriptions vet_prescription:text prescribed_by:text disease:text livestock:text completed:boolean 

Run Command นี้ Rails Scaffold Initial Structure สำหรับ Migration, Test, Model, Controller, Route:

rails db:migrate 

Command นี้ Add Tables เข้า Database

Seeding the Database

Seed Database ด้วย Prescription Data เพิ่ม Code Snippet นี้ใน db/migrate/seed.rb

//db/migrate/seed.rb Prescription.destroy_all Prescription.create!([{ vet_prescription:"Achyranthes aspera", prescribed_by:"Dr Chucks", disease:"Rabbies", livestock:"goat", completed:false }, { vet_prescription:"Achyranthes aspera", prescribed_by:"Dr Rex", disease:"Rabbies", livestock:"Dog", completed:false }, { vet_prescription:"ethnovet", prescribed_by:"Dr Chucks", disease:"Pox", livestock:"Sheep", completed:false }]) p "Created #{Prescription.count} prescriptions" 

File นี้ Store Initial Data สำหรับ Seed Database เมื่อ App Start Up เราจะมี Existing Prescription Data

Run Command Execute Code ใน db/migrate/seed.rb Seed Database:

rails db:seed 

With These Few Commands เราสร้าง Functional CRUD API ด้วย Rails Easy มากไหม? (Smiles)

CORS Configuration

เนื่องจากเรา Expose CRUD APIs ให้ Frontend และ Frontend/Backend Servers รัน Different PORT ต้อง Setup CORS Configuration บน Rails Backend Grant Access Data Sharing ระหว่าง Frontend และ Backend

Locate Gemfile ใน Project Root Uncomment Line:

# gem 'rack-cors' 

Add Code ใน config/environments/initializers/cors.rb:

//config/environments/initializers/cors.rb Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end 

Snippet นี้ Allow Rails CRUD API Accessible จาก Frontend Apps รัน Any PORT

Run Command Start Rails Server:

rails s 

Navigate localhost:3000/prescriptions ได้ JSON Response สำหรับ All Prescriptions

Consider Using Rails ถ้าทำงานกับ Clients ที่ Change Project Workflows บ่อย ด้วย Rails Implement Features ด้วย Few Commands และ Lines of Code นี่ Personal Opinion

Introducing Vue

Vue เป็น Progressive Framework สำหรับ Building User Interfaces Vue Introduce Concepts เช่น Virtual DOM, Render Functions, และ Server-side Rendering Capabilities ให้ Significant Performance Optimization สำหรับ Web Applications

Vue 3 มาพร้อม New Features และ Changes มากสำหรับ Developers Features นี้ Designed เพื่อ Improve Overall Stability, Speed และ Maintainability

เราจะใช้ Composition API หนึ่งใน Most Anticipated Features ของ Vue 3 มัน New Way Create Vue Components ด้วย Organized และ Efficient Approach เขียน Code และ Full TypeScript Type-checking Support

Introducing Vuex

Vuex เป็น State Management Library สร้างโดย Vue Team Based on Same Flux Architecture as Redux Designed Specifically สำหรับ Vue Allow Better Organization ของ Store Vuex จะ Crucial ถ้า Vue Application State Gets More Complex As It Grows Latest Stable Release Vuex v4.0.0 Support Composition API ใน Vue 3 และ Robust Inference สำหรับ TypeScript

Setting up the Frontend Application

Frontend Setup ด้วย Vue 3 และ TypeScript ในขณะที่ Vuex ใช้สำหรับ Application State Management

Start by Using Vue-CLI Tool Create Vue 3 App With TypeScript Support

Install Vue-CLI Tool Globally:

npm install --global @vue/cli 

Create New Vue 3 App With TypeScript และ Vuex Support:

vue create vet_clinic_frontend 

Choose Manually Select Feature Option Hit Space Key Select Options:

  • Choose Vue Version
  • Babel
  • TypeScript
  • Linter / Formatter

Next Choose Vue 3.x(Preview) as Version for Project

  • Enter Yes Use Class-style Component Syntax
  • Enter Yes Use Babel Alongside TypeScript
  • Select Any Linter of Choice

Once Vue 3 App Generated Successfully เราจะมี Vue 3 Project Setup พร้อม Vuex (V4.x) และ Full TypeScript Support

เราจะ Add Type Safety ด้วย TypeScript Start Dev Server Run Command ใน Terminal ไป https://localhost:8080 Preview Project in Browser

เราจะใช้ Bulma CSS Framework สำหรับ Styling App Run Command Install Bulma CSS:

npm install bulma 

Import Bulma CSS Add Code ใน App.vue:

//App.vue <style lang="scss"> @import "~bulma/css/bulma.css"; </style> 

Setting up the Application Store

Application Store Setup ด้วย Vuex To Make Changes to Store Actions Dispatched from Component Triggers Mutations Thereby Updating Store

Take Steps Setup Application Store:

  1. Create State Objects
  2. Set Up Mutations That Will Occur
  3. Create Actions That Commit to Subsequent Mutations
  4. Create Getters for Components to Directly Compute State Data

State

State คือ Store Object ที่ Hold Application-level Data ที่ต้อง Accessed by All Components

Create state.ts File in Store Directory With Code:

//src/store/state.ts export type Prescription = { id: number; vet_prescription: string; prescribed_by: string; disease: string; livestock: string; completed: boolean; editing: boolean; }; export type Data = { vet_prescription: string; prescribed_by: string; disease: string; livestock: string; }; export type State = { loading: boolean; prescriptions: Prescription[]; data: Data | null; showCreateModal: boolean; showEditModal: boolean; showPrescriptionModal: boolean; editModalPrescriptionId: number | undefined; showPrescriptionId: number | undefined; }; export const state: State = { loading: false, prescriptions: [], data: null, showCreateModal: false, showEditModal: false, showPrescriptionModal: false, editModalPrescriptionId: undefined, showPrescriptionId: undefined, }; 

ที่นี่เรา Add Type Safety ให้ Prescription และ Data เรา Export Types เพราะจะ Used ใน Definitions ของ Getters, Mutations, และ Actions สุดท้าย Cast State Type ให้ state

Mutations

Mutations คือ Methods ที่เมื่อ Triggered Modify Store พวกมัน Receive State as First Argument และ Payload as Second Eventually Modify Application State with Payload To Create Mutations Vuex Docs Recommend Using Constants for Mutation Types

Create mutations.ts File in Store Directory With Code:

//src/store/mutations.ts import { MutationTree } from "vuex"; import { State, Prescription, Data } from "./state"; export enum MutationType { CreatePrescription = "CREATE_PRESCRIPTION", SetPrescriptions = "SET_PRESCRIPTIONS", CompletePrescription = "COMPLETE_PRESCRIPTION", RemovePrescription = "REMOVE_PRESCRIPTION", EditPrescription = "EDIT_PRESCRIPTION", UpdatePrescription = `UPDATE_PRESCRIPTION`, SetLoading = "SET_LOADING", SetCreateModal = "SET_CREATE_MODAL", SetEditModal = "SET_EDIT_MODAL", SetPrescriptionModal = "SET_PRESCRIPTION_MODAL", } 

Snippet นี้ใช้ Enum Symbol Hold All Possible Names of Mutations ใน App

Next Declare Contract (Type) สำหรับแต่ละ MutationType:

//src/store/mutation.ts export type Mutations = { [MutationType.CreatePrescription](state: State, prescription: Data): void; [MutationType.SetPrescriptions](state: State, prescription: Prescription[]): void; [MutationType.CompletePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void; [MutationType.RemovePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void; [MutationType.EditPrescription](state: State, prescription: Partial<Prescription> & { id: number }): void; [MutationType.UpdatePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void; [MutationType.SetLoading](state: State, value: boolean): void; [MutationType.SetCreateModal](state: State, value: boolean): void; [MutationType.SetEditModal](state: State, value: { showModal: boolean; prescriptionId: number | undefined }): void; [MutationType.SetPrescriptionModal](state: State, value: { showModal: boolean; prescriptionId: number | undefined }): void; }; 

Then Implement Contracts Declared สำหรับแต่ละ MutationType:

//src/store/mutation.ts export const mutations: MutationTree<State> & Mutations = { [MutationType.CreatePrescription](state, prescription) { state.data == prescription; }, [MutationType.SetPrescriptions](state, prescriptions) { state.prescriptions = prescriptions; }, [MutationType.CompletePrescription](state, newPrescription) { const prescription = state.prescriptions.findIndex((prescription) => prescription.id === newPrescription.id); if (prescription === -1) return; state.prescriptions[prescription] = { ...state.prescriptions[prescription], ...newPrescription }; }, [MutationType.RemovePrescription](state, Prescription) { const prescription = state.prescriptions.findIndex((prescription) => prescription.id === Prescription.id); if (prescription === -1) return; //If prescription exist in the state, remove it state.prescriptions.splice(prescription, 1); }, [MutationType.EditPrescription](state, Prescription) { const prescription = state.prescriptions.findIndex((prescription) => prescription.id === Prescription.id); if (prescription === -1) return; //If prescription exist in the state, toggle the editing property state.prescriptions[prescription] = { ...state.prescriptions[prescription], editing: !state.prescriptions[prescription].editing }; console.log("prescription", state.prescriptions[prescription]); }, [MutationType.UpdatePrescription](state, Prescription) { state.prescriptions = state.prescriptions.map((prescription) => { if (prescription.id === Prescription.id) { return { ...prescription, ...Prescription }; } return prescription; }); }, [MutationType.SetLoading](state, value) { state.loading = value; }, [MutationType.SetCreateModal](state, value) { state.showCreateModal = value; }, [MutationType.SetEditModal](state, value) { state.showEditModal = value.showModal; state.editModalPrescriptionId = value.prescriptionId; }, [MutationType.SetPrescriptionModal](state, { showModal, prescriptionId }) { state.showPrescriptionModal = showModal; state.showPrescriptionId = prescriptionId; }, }; 

MutationTree เป็น Generic Type Shipped with Vuex Package เราใช้ใน Snippet นี้ Declare Type of Mutation Tree Mutation Tree และ Mutations Ensure Contract Implemented Correctly ไม่งั้น Typescript Trigger Error

Actions

Actions คือ Methods ที่ Trigger Mutations When Handling Asynchronous Tasks เช่น Making Request to API Actions Used Before Calling Corresponding Mutations with API Response as Payload เราจะได้ Clear Implementation สำหรับ Scenario นี้ขณะ Create Actions

Before Creating Actions Install Axios Handle All Http Requests to Rails Server:

npm install axios --save 

Create actions.ts File in Store Directory With Code:

//src/store/actions.ts import { ActionContext, ActionTree } from "vuex"; import { Mutations, MutationType } from "./mutations"; import { State, Prescription, Data } from "./state"; import axios from "axios"; const apiUrl = "https://localhost:3000/prescriptions"; export enum ActionTypes { GetPrescriptions = "GET_PRESCRIPTIONS", SetCreateModal = "SET_CREATE_MODAL", SetEditModal = "SET_EDIT_MODAL", RemovePrescription = "REMOVE_PRESCRIPTION", CreatePrescription = "CREATE_PRESCRIPTION", UpdatePrescription = "UPDATE_PRESCRIPTION", } 

Similarly Snippet นี้ใช้ Enum Symbol Hold All Possible Names of Actions ใน App

Next Declare Contract (Type) สำหรับแต่ละ ActionType:

//src/store/actions.ts type ActionAugments = Omit<ActionContext<State, State>, "commit"> & { commit<K extends keyof Mutations>(key: K, payload: Parameters<Mutations[K]>[1]): ReturnType<Mutations[K]>; }; export type Actions = { [ActionTypes.GetPrescriptions](context: ActionAugments): void; [ActionTypes.SetCreateModal](context: ActionAugments): void; [ActionTypes.SetEditModal](context: ActionAugments): void; [ActionTypes.RemovePrescription](context: ActionAugments, Prescription: { id: number }): void; [ActionTypes.CreatePrescription](context: ActionAugments, data: Data): void; [ActionTypes.UpdatePrescription](context: ActionAugments, prescription: Prescription): void; }; 

ActionAugments Type Restricts All Commits Only to Declared Mutations และ Check Payload Type

Next Implement Contract (Types) Declared สำหรับแต่ละ ActionType Add Code Below to action.ts File:

//src/store/actions.ts export const actions: ActionTree<State, State> & Actions = { async [ActionTypes.GetPrescriptions]({ commit }) { commit(MutationType.SetLoading, true); const response = await axios.get(apiUrl); commit(MutationType.SetLoading, false); commit(MutationType.SetPrescriptions, response.data); }, async [ActionTypes.SetCreateModal]({ commit }) { commit(MutationType.SetCreateModal, true); }, async [ActionTypes.SetEditModal]({ commit }) { commit(MutationType.SetEditModal, { showModal: true, prescriptionId: 1 }); }, //Optimistic update async [ActionTypes.RemovePrescription]({ commit }, Prescription) { if (Prescription != undefined) { commit(MutationType.RemovePrescription, Prescription); } const response = await axios.delete(`${apiUrl}/${Prescription.id}`); }, async [ActionTypes.CreatePrescription]({ commit, dispatch }, Prescription) { const response = await axios.post(apiUrl, Prescription); dispatch(ActionTypes.GetPrescriptions); }, async [ActionTypes.UpdatePrescription]({ commit, dispatch }, Prescription) { if (Prescription != undefined) { commit(MutationType.UpdatePrescription, Prescription); const response = await axios.patch(`${apiUrl}/${Prescription.id}`, Prescription); dispatch(ActionTypes.GetPrescriptions); } }, }; 

ที่นี่เราสร้าง Actions Variable เก็บ All Implemented Actions Similarly ActionTree<State> & Actions Ensures Contract (type Actions) Implemented Correctly ไม่งั้น Typescript Trigger Error

เรา Setup Asynchronous Call to Rails API Endpoint ใน GetPrescriptions Action และ Trigger SetPrescriptions Mutation Type with Response Data as Payload เรายัง Setup SetCreateModal, SetEditModal, CreatePrescription, UpdatePrescription, และ RemovePrescription Actions

Getters

Getters คือ Methods ที่ Receive State as First Parameter และ Return Computed Information from Store State

Create getters.ts File in Store Directory With Code:

//src/store/getters.ts import { GetterTree } from "vuex"; import { State, Prescription } from "./state"; export type Getters = { completedPrescriptionCount(state: State): number; totalPrescriptionCount(state: State): number; getPrescriptionById(state: State): (id: number) => Prescription | undefined; }; export const getters: GetterTree<State, State> & Getters = { completedPrescriptionCount(state) { return state.prescriptions.filter((prescription) => prescription.completed).length; }, totalPrescriptionCount(state) { return state.prescriptions.length; }, getPrescriptionById: (state) => (id: number) => { return state.prescriptions.find((prescription) => prescription.id === id); }, }; 

Code Snippet นี้ Define Getters ต่อไป:

  • completedPrescriptionCount – Function Gets Total Number of Completed Prescriptions ใน State
  • totalPrescriptionCount – Function Gets Total Number of Prescriptions ใน State
  • getPrescriptionById – Function Gets Prescription by its ID

เรายัง Add Type Safety ให้ Getters

Store

Wire state, mutations, actions, และ getters เข้า Global Vuex Store Update store/index.ts:

//src/store/index.ts 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, }); 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]>; }; }; 

state, mutations, actions, และ getters Required to Create Store by Passing Object Containing Them into createStore Method During Development createLogger Plugin Logs State (Previous State and Next State) และ Mutations to Console To Have Store Accessible in All Components เราต้อง Inject It Into Entire Application Fortunately Vue-CLI Tool Already Imported Entire Store และ Passed It Within Application's Vue Instance

Integrating the Vuex Store into the Component

ใน Tutorial นี้ Vue 3 Composition API จะใช้ Create All Components ใน Frontend Application

App Component

Frontend ควร Render List of Prescription Data As Soon As It Launches เราจะ Dispatch GetPrescription Action Within Component's mounted() Lifecycle Hook To Access Store Within Component useStore Hook ซึ่ง Returns Our Store จะ Execute

//src/App.vue <script lang="ts"> import { computed, defineComponent, onMounted } from "vue"; import PrescriptionList from "./components/PrescriptionList.vue"; import { useStore } from "./store"; import { ActionTypes } from "./store/actions"; export default defineComponent({ components: { PrescriptionList }, setup() { const store = useStore(); const loading = computed(() => store.state.loading); onMounted(() => store.dispatch(ActionTypes.GetPrescriptions)); const completedCount = computed(() => store.getters.completedPrescriptionCount); const totalCount = computed(() => store.getters.totalPrescriptionCount); return { loading, completedCount, totalCount }; }, }); </script> <template> <nav class="navbar" role="navigation" aria-label="main navigation"> <div class="navbar-brand"> <a class="navbar-item" href="https://bulma.io"> <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28" /> </a> </div> <div id="navbarBasicExample" class="navbar-menu"> <div class="navbar-start"> <a class="navbar-item"> Home </a> <a class="navbar-item"> About </a> </div> </div> </nav> <div class="container mx-auto mt-4"> <h1 class="is-size-3 has-text-centered p-2 has-text-weight-bold is-success">Vet clinic Frontend</h1> <h3 class="has-text-centered p-2">Manage records of treated livestock in your farm</h3> <div v-if="loading"> <h3 class="has-text-centered mt-4">Loading...</h3> </div> <div v-else> <p class="has-text-centered mt-2">{{ completedCount }} of {{ totalCount }} treated.</p> <PrescriptionList /> </div> </div> </template> <style> @import "~bulma/css/bulma.css"; </style> 

ที่นี่เราสร้าง Three Computed Properties:

  • completedCount เรียก completedPrescriptionCount Getter Method Retrieve Total Completed Treatments
  • totalCount เรียก totalPrescriptionCount Getter Method Retrieve Total Prescriptions
  • loading Gets Loading Property of State

With Vue 3 Composition API Methods และ Properties ที่ Template Require ต้อง Return เพื่อ Accessible in Template Notice How We Returned loading, completedCount, and totalCount

PrescriptionList Component

Component นี้ Responsible for Retrieve List of Prescriptions from Backend และ Pass Prescription Data ให้ Child Component

Create PrescriptionList.vue Inside Components Folder With Code:

//src/components/PrescriptionList.vue <template> <table class="table is-hoverable is-striped"> <thead> <tr> <th><abbr title="Position">Prescription Id</abbr></th> <th>Treated</th> <th>Prescription</th> <th><abbr title="Won">Prescribed By</abbr></th> <th><abbr title="Drawn">Disease</abbr></th> <th><abbr title="Drawn">Livestock</abbr></th> <th><abbr title="Lost">Actions</abbr></th> </tr> </thead> <tbody v-if="prescriptions"> <tr v-for="prescription in prescriptions" :key="prescription.id"> <PrescriptionListItem v-bind="prescription" /> </tr> </tbody> <tfoot> <CreateModal v-show="showCreateModal"></CreateModal> <button class="button is-success" @click="setModal">Create Prescription</button> </tfoot> </table> <EditModal v-if="showEditModal" :id="editModalPrescriptionId"></EditModal> <Prescription v-if="showPrescriptionModal" :id="showPrescriptionId"></Prescription> </template> <script> import CreateModal from "./CreateModal"; import EditModal from "./EditModal"; import Prescription from "./Prescription"; import PrescriptionListItem from "./PrescriptionListItem"; import { defineComponent, computed } from "vue"; import { useStore } from "@/store"; import { MutationType } from "@/store/mutations"; export default defineComponent({ name: "Table", components: { CreateModal, PrescriptionListItem, Prescription, EditModal, }, setup() { const store = useStore(); const setModal = () => { store.commit(MutationType.SetCreateModal, true); }; const showCreateModal = computed(() => store.state.showCreateModal); const showEditModal = computed(() => store.state.showEditModal); const editModalPrescriptionId = computed(() => store.state.editModalPrescriptionId); const showPrescriptionModal = computed(() => store.state.showPrescriptionModal); const showPrescriptionId = computed(() => store.state.showPrescriptionId); const prescriptions = computed(() => store.state.prescriptions); return { showCreateModal, setModal, prescriptions, showEditModal, showPrescriptionModal, editModalPrescriptionId, showPrescriptionId }; }, }); </script> <style scoped> table { width: 100%; } .fa { font-size: 1.2rem; margin-left: 15px; } .fa:hover { font-size: 1.4rem; } </style> 

setModal Method Invokes Mutation ที่ Set showCreateModal in State to True Resulting in Launching Modal สำหรับ Creating Prescriptions

เราสร้าง Computed Properties:

  • showCreateModal Gets showCreateModal Property of State
  • showEditModal Gets showEditModal Property of State
  • showPrescriptionModal Gets showPrescriptionModal Property of State
  • prescription Gets List of Prescription from State
  • showPrescriptionId Gets showPrescriptiond Property of State
  • editPrescriptionId Gets editPrescriptionId Property of State

Prescription Component

Component นี้ Receive Prescription ID as Prop from PrescriptionList Component ID Props Used to Fetch Prescription with Corresponding ID via getPrescriptionById Getters Method และ Render Prescription Properties in Browser

Create Prescription.vue Inside Components Folder With Code:

//src/components/Prescription.vue <template> <div class="modal is-active"> <div class="modal-background"></div> <div class="modal-content"> <h1>VIEW PRESCRIPTION</h1> <div class="card"> <div class="card-content"> <div class="media"> <div class="media-content"> <p class="title is-4">Livestock: {{ prescription.livestock }}</p> <p class="subtitle is-6"><b>Prescribed by:</b> {{ prescription.prescribed_by }}</p> <p class="subtitle is-6"><b>Disease:</b> {{ prescription.disease }}</p> </div> </div> <div class="content"> <p class="subtitle is-6">Prescription: {{ prescription.vet_prescription }}</p> </div> </div> </div> </div> <button class="modal-close is-large" @click="closeModal" aria-label="close"></button> </div> </template> <script lang="ts"> import { computed } from "vue"; import { useStore } from "@/store"; import { MutationType } from "@/store/mutations"; export default { name: "PrescriptionModal", props: { id: { type: Number, required: true }, }, setup(props: any) { const store = useStore(); const prescription = computed(() => store.getters.getPrescriptionById(Number(props.id))); const closeModal = () => { store.commit(MutationType.SetPrescriptionModal, { showModal: false, prescriptionId: undefined, }); }; return { closeModal, prescription }; }, }; </script> <style scoped> h1 { color: #ffffff; text-align: center; font-size: 2rem; margin-bottom: 3rem; } </style> 

closeModal Method Commits SetPrescriptionModal Mutation ซึ่ง Set showModal Property in State to False ในขณะที่ prescription Computed Property เรียก getPrescriptionById Getter Method Retrieve Prescription by Id

CreateModal Component

Component นี้ Responsible for Creating Prescriptions

Create CreateModal.vue Inside Components Folder With Code:

//src/components/CreateModal.vue <template> <div class="modal is-active"> <div class="modal-background"></div> <div class="modal-content"> <form @submit.prevent="createPrescription"> <div class="field"> <label class="label">Prescribed By</label> <div class="control"> <input v-model="prescribedBy" class="input" type="text" placeholder="Enter prescriber's name" /> </div> </div> <div class="field"> <label class="label">Prescription</label> <div class="control"> <textarea v-model="prescription" class="textarea" placeholder="Enter prescription"></textarea> </div> </div> <div class="field"> <label class="label">Disease</label> <div class="control"> <input v-model="disease" class="input" type="text" placeholder="Enter name of disease" /> </div> </div> <div class="field"> <label class="label">Livestock</label> <div class="control"> <input v-model="livestock" class="input" type="text" placeholder="Enter livestock" /> </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> </template> <script lang="ts"> import { defineComponent, reactive, toRefs } from "vue"; import { useStore } from "@/store"; import { Data } from "@/store/state"; import { MutationType } from "@/store/mutations"; import { ActionTypes } from "@/store/actions"; export default { name: "CreateModal", setup() { const state = reactive({ prescription: "", prescribedBy: "", disease: "", livestock: "", }); const store = useStore(); const createPrescription = () => { if (state.prescription === "" || state.prescribedBy === "" || state.disease === "" || state.livestock === "") return; const prescription: Data = { vet_prescription: state.prescription, prescribed_by: state.prescribedBy, disease: state.disease, livestock: state.livestock, }; store.dispatch(ActionTypes.CreatePrescription, prescription); state.prescription = ""; state.prescribedBy = ""; state.disease = ""; state.livestock = ""; }; const closeModal = () => { store.commit(MutationType.SetCreateModal, false); }; return { closeModal, ...toRefs(state), createPrescription }; }, }; </script> 

createPrescription Method Dispatches Action ที่ Make Post Request to Server Thereby Creating New Prescription ในขณะที่ closeModal Method Commits SetPrescriptionModal Mutation (ซึ่ง Set ShowModal Property in State to False)

Working with Forms and Input Elements Requires Two-way Data Binding และ As Such เราใช้ Vue 3's reactive Method Store Values Used in Input Fields

Note: When Using reactive We Need to Use toRefs to Convert Reactive Object to Plain Object ที่ Each Property on Resulting Object Is a Ref Pointing to Corresponding Property in Original Object

EditModal Component

Component นี้ Responsible for Updating Prescriptions Logic คล้าย CreatePrescription Component ที่ Discussed ใน Previous Section

Create EditModal.vue Inside Components Folder With Code:

//src/components/EditModal.vue <template> <div class="modal is-active"> <div class="modal-background"></div> <div class="modal-content"> <form @submit.prevent="updatePrescription"> <h1>Edit Modal</h1> <div class="field"> <label class="label">Prescribed By</label> <div class="control"> <input v-model="prescribedBy" class="input" type="text" placeholder="Enter prescriber's name" /> </div> </div> <div class="field"> <label class="label">Prescription</label> <div class="control"> <textarea v-model="prescription" class="textarea" placeholder="Enter Prescription"></textarea> </div> </div> <div class="field"> <label class="label">Disease</label> <div class="control"> <input v-model="disease" class="input" type="text" placeholder="Enter name of disease" /> </div> </div> <div class="field"> <label class="label">Livestock</label> <div class="control"> <input v-model="livestock" class="input" type="text" placeholder="Enter livestock" /> </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> </template> <script lang="ts"> import { defineComponent, reactive, toRefs, computed, onMounted } from "vue"; import { useStore } from "@/store"; import { Prescription } from "@/store/state"; import { MutationType } from "@/store/mutations"; import { ActionTypes } from "@/store/actions"; export default { name: "EditModal", props: { id: { type: Number, required: true }, }, setup(props: any) { const state = reactive({ prescription: "", prescribedBy: "", disease: "", livestock: "", }); const store = useStore(); const setFields = () => { const prescription = store.getters.getPrescriptionById(Number(props.id)); if (prescription) { console.log("prescription si kolo", prescription); state.prescription = prescription.vet_prescription; state.prescribedBy = prescription.prescribed_by; state.disease = prescription.disease; state.livestock = prescription.livestock; } }; onMounted(() => { setFields(); }); const updatePrescription = () => { if (state.prescription === "" || state.prescribedBy === "" || state.disease === "" || state.livestock === "") return; const prescription: Prescription = { id: props.id, vet_prescription: state.prescription, prescribed_by: state.prescribedBy, disease: state.disease, livestock: state.livestock, completed: false, editing: false, }; store.dispatch(ActionTypes.UpdatePrescription, prescription); state.prescription = ""; state.prescribedBy = ""; state.disease = ""; state.livestock = ""; }; const closeModal = () => { store.commit(MutationType.SetEditModal, { showModal: false, prescriptionId: undefined }); }; return { closeModal, ...toRefs(state), updatePrescription }; }, }; </script> <style scoped> label { color: #ffffff; } h1 { color: #ffffff; text-align: center; font-size: 2rem; margin-bottom: 3rem; } </style> 

createPrescription Method Dispatches Action ที่ Make Put Request to Server Thereby Updating Existing Prescription by its ID ในขณะที่ closeModal Method Commits SetPrescriptionModal Mutation That Sets showModal Property in State to False Calling setFields Method on onMounted Lifecycle Hook Triggers getPrescriptionById Getters Method Fetch Prescription from Store by its ID และ Then Updates Properties in Reactive Object with Fetched Prescription Properties As Soon As Component Is Mounted on DOM

Launching the Final Project

ใน Root Directory ของ Rails API Server Run Command Start Server:

rails server 

ตอนนี้ Run Frontend Application With Command:

npm run serve 

Finally Application ควรเป็น:

เชี่ยวชาญ Full-Stack Development ด้วย Vue, Vuex, และ Rails: แนวทาง Scalable

Conclusion

เราสร้าง CRUD API Server ด้วย Rails และ Frontend Application บน Vue 3 Composition API และ Vuex ทั้งหมดรัน Different Servers ในขณะที่ Integrate Both Stacks Build Fullstack Web Application ผมหวังว่าคุณเรียนรู้มากจาก Tutorial นี้ Please Reach Out in Comment Section Below ถ้ามี Questions หรือ Suggestions Here is the GitHub Repo for the Full-stack Application Built in This Tutorial